Легальность реализации COW std::string в C++11

Насколько я понял, копирование при записи не является жизнеспособным способом реализации соответствующего std::string в C++11, но когда он недавно обсуждался, я обнаружил, что не могу напрямую поддержать это утверждение.

Правильно ли я понимаю, что С++ 11 не допускает реализации std::string на основе COW?

Если да, указано ли это ограничение явно где-то в новом стандарте (где)?

Или это ограничение подразумевается в том смысле, что совокупный эффект новых требований к std::string исключает реализацию std::string на основе COW. В этом случае меня бы заинтересовал вывод в стиле главы и стиха «С++ 11 эффективно запрещает реализации std::string на основе COW».


person acm    schedule 30.08.2012    source источник
comment
Ошибка GCC для их строки COW: gcc.gnu.org/bugzilla/ show_bug.cgi?id=21334#c45 . Одной из ошибок, отслеживающих новую реализацию std::string в компиляторе C++11 в libstdc++, является gcc.gnu.org/bugzilla/show_bug.cgi?id=53221   -  person user7610    schedule 01.05.2014


Ответы (7)


Это не разрешено, потому что согласно стандарту 21.4.1 p6 аннулирование итераторов/ссылок разрешено только для

— в качестве аргумента любой стандартной библиотечной функции, принимающей в качестве аргумента ссылку на неконстантную базовую_строку.

— Вызов неконстантных функций-членов, кроме operator[], at, front, back, begin, rbegin, end и rend.

Для строки COW вызов неконстантной operator[] потребует создания копии (и аннулирования ссылок), что запрещено абзацем выше. Следовательно, в С++ 11 больше не разрешено иметь строку COW.

person Dave S    schedule 30.08.2012
comment
Некоторое обоснование: N2534 - person M.M; 18.12.2014
comment
-1 Логика не выдерживает никакой критики. Во время копирования COW нет ссылок или итераторов, которые могут быть признаны недействительными, весь смысл копирования заключается в том, что такие ссылки или итераторы сейчас получаются, поэтому копирование необходимо. Но все же может случиться так, что С++ 11 запрещает реализации COW. - person Cheers and hth. - Alf; 18.12.2014
comment
@Cheersandhth.-Alf: Если бы COW был разрешен, логику можно увидеть в следующем: std::string a("something"); char& c1 = a[0]; std::string b(a); char& c2 = a[1]; c1 является ссылкой на a. Затем вы копируете a. Затем, когда вы пытаетесь получить ссылку во второй раз, она должна сделать копию, чтобы получить неконстантную ссылку, поскольку есть две строки, указывающие на один и тот же буфер. Это должно аннулировать первую сделанную ссылку и противоречит приведенному выше разделу. - person Dave S; 18.12.2014
comment
@DaveS: В случае, если вы делаете набросок, совместное использование буфера отсутствует, поскольку существуют (возможные) внешние ссылки на буфер исходной строки. Таким образом, вывод о более позднем копировании неверен. В довершение всего предположение о том, что это приведет к аннулированию ссылок на буфер исходной строки, также неверно, но очень неуместно, поскольку позднего копирования здесь не происходит. В более общем случае логически невозможно построить пример, который делает то, что вы хотите, потому что COW предназначен для того, чтобы избежать этого случая. Вот почему каждая попытка заканчивается множеством ошибок. - person Cheers and hth. - Alf; 18.12.2014
comment
@Cheersandhth.-Alf, согласно этому, по крайней мере Реализация COW в GCC делает именно то, что говорит DaveS. Так что по крайней мере этот стиль COW запрещен стандартом. - person Tavian Barnes; 18.01.2015
comment
@BenVoigt: голосование против остается в силе: ответ неверен и основан на простых заблуждениях. Читатели могут продемонстрировать это сами, попытавшись построить конкретный пример. (Тавиан Барнс ссылается на отчет об ошибке для gcc и утверждает, что ошибка демонстрирует, что правильная реализация не может работать в рамках правил стандарта. Это снова заблуждение. Вы думаете, это показывает, что мои комментарии неверны?) - person Cheers and hth. - Alf; 05.03.2015
comment
@Alf: В этом ответе утверждается, что неконстантный operator[] (1) должен сделать копию и что (2) это незаконно. С каким из этих двух пунктов вы не согласны? Глядя на ваш первый комментарий, кажется, что реализация может совместно использовать строку, по крайней мере, в соответствии с этим требованием, до тех пор, пока к ней не будет осуществлен доступ, но для чтения и записи потребуется отменить ее совместное использование. Это ваши рассуждения? - person Ben Voigt; 05.03.2015
comment
@BenVoigt: цитируя себя, во время копирования COW нет ссылок или итераторов, которые могут быть признаны недействительными. Утверждение, что копирование обязательно делает ссылки недействительными, просто неверно. Никакого примера построить нельзя, потому что это неправда. И я уверен, что вы это уже поняли. - person Cheers and hth. - Alf; 22.03.2015
comment
Для ясности: под COW-копированием вы имеете в виду увеличение счетчика общего доступа или отказ от общего доступа? - person Ben Voigt; 22.03.2015
comment
Нераспространение. Прости. Я думал, что эта копия будет означать... копирование. - person Cheers and hth. - Alf; 22.03.2015
comment
@BenVoigt: я добавил правильный ответ. Вопреки тому, что я ожидал (но как я оставил открытым в своем первом комментарии здесь в этом списке), действительно практически невозможно предоставить соответствующую реализацию с подсчетом ссылок для C++ 11 и более поздних версий из-за новых требований, не связанных с бросками. - person Cheers and hth. - Alf; 22.03.2015
comment
@Cheersandhth.-Альф, что не так с моими рассуждениями на gcc.gnu .org/bugzilla/show_bug.cgi?id=21334#c47 ? - person Jonathan Wakely; 22.03.2015
comment
@JonathanWakely: я не вижу в этом ничего плохого. Это прекрасный отчет об ошибке. Ваши рассуждения (точнее наблюдения) здесь кажутся на 100% верными. С другой стороны, мнение о том, что эта ошибка означает что-то относительно формального, что утверждает кто-то другой, является нонсенсом. - person Cheers and hth. - Alf; 22.03.2015
comment
Давайте продолжим обсуждение в чате. - person Jonathan Wakely; 22.03.2015
comment
@Cheersandhth.-Alf, я добавил свой собственный ответ, который просто расширяет правильный ответ Дейва С. выше. Может быть, если вы сейчас перечитаете мои комментарии выше, вы увидите, что я просил вас дать какое-то обоснование вашим собственным, казалось бы, экстраординарным утверждениям, таким как С другой стороны, точка зрения, что эта ошибка означает что-то формальное, что кто-то еще утверждал, является чушью. (это была моя собственная точка зрения, которую вы назвали чушью, но меня обвиняют в личных нападках и переходе на личности, когда я использую одно и то же слово! Как оскорбительно!) - person Jonathan Wakely; 23.03.2015
comment
@DaveS Это действительно недействительно? Потому что c1 все еще жив благодаря b. Это правда, что &c1 == &a[0] больше не соответствует действительности, но c1 является ссылкой на объект (область хранения самого персонажа), а не на имя (a[0]), и объект все еще жив, поэтому ссылка в порядке. -определенный. - person Peregring-lk; 29.10.2016

Ответы Dave S и gbjbaanb правильно. (И Люк Дантон тоже прав, хотя это скорее побочный эффект запрета строк COW, чем исходное правило, которое запрещает это.)

Но чтобы прояснить некоторую путаницу, я собираюсь добавить еще несколько экспозиций. Различные комментарии ссылаются на мой комментарий об ошибке GCC, который дает следующий пример:

std::string s("str");
const char* p = s.data();
{
    std::string s2(s);
    (void) s[0];
}
std::cout << *p << '\n';  // p is dangling

Смысл этого примера в том, чтобы продемонстрировать, почему строка с подсчетом ссылок (COW) GCC недействительна в C++11. Стандарт C++11 требует, чтобы этот код работал правильно. Ничто в коде не позволяет сделать p недействительным в C++11.

Используя старую реализацию std::string с подсчетом ссылок в GCC, этот код имеет неопределенное поведение, потому что p является недействительным, становясь висячим указателем. (Происходит следующее: когда создается s2, он разделяет данные с s, но получение неконстантной ссылки через s[0] требует, чтобы данные не были разделены, поэтому s выполняет «копирование при записи», потому что ссылка s[0] потенциально может использоваться для запись в s, затем s2 выходит за пределы области видимости, уничтожая массив, на который указывает p).

Стандарт C++03 явно разрешает такое поведение в 21.3 [lib.basic.string] p5, где говорится, что после вызова data() первый вызов operator[]() может сделать указатели, ссылки и итераторы недействительными. Таким образом, строка COW GCC была корректной реализацией C++03.

Стандарт C++11 больше не разрешает такое поведение, потому что ни один вызов operator[]() не может сделать недействительными указатели, ссылки или итераторы, независимо от того, следуют ли они за вызовом data().

Таким образом, приведенный выше пример должен работать в C++11, но не работает со строкой COW типа libstdc++, поэтому такая строка COW не разрешена в C++. 11.

person Jonathan Wakely    schedule 22.03.2015
comment
Реализация, которая отменяет совместное использование при вызове .data() (и при каждом возвращении указателя, ссылки или итератора), не страдает от этой проблемы. т.е. (инвариант) буфер в любое время не используется совместно или совместно используется без внешних ссылок. Я думал, что вы задумали комментарий к этому примеру как неформальный отчет об ошибке в качестве комментария, очень сожалею о неправильном понимании этого! Но, как вы можете видеть, рассмотрев описанную здесь реализацию, которая отлично работает в C++11, когда требования noexcept игнорируются, в примере ничего не говорится о формальных. Я могу предоставить код, если хотите. - person Cheers and hth. - Alf; 23.03.2015
comment
Если вы отменяете общий доступ почти при каждом доступе к строке, вы теряете все преимущества совместного использования. Реализация COW должна быть практичной, чтобы стандартная библиотека могла использовать ее как std::string, и я искренне сомневаюсь, что вы сможете продемонстрировать полезную, производительную строку COW, отвечающую требованиям аннулирования C++11. Поэтому я утверждаю, что спецификации noexcept, которые были добавлены в последнюю минуту, являются следствием запрета строк COW, а не основной причиной. N2668 кажется совершенно ясным, почему вы продолжаете отрицать изложенные там четкие доказательства намерений комитета? - person Jonathan Wakely; 23.03.2015
comment
Кроме того, помните, что data() является константной функцией-членом, поэтому должно быть безопасно вызывать ее одновременно с другими константными членами и, например, вызывать data() одновременно с другим потоком, создающим копию строки. Таким образом, вам понадобятся все накладные расходы мьютекса для каждой строковой операции, даже константной, или сложность свободной от блокировки изменяемой структуры с подсчетом ссылок, и в конце концов вы получите только совместное использование, если вы никогда не изменяете или не получаете доступ к своим строкам, поэтому многие, многие строки будут иметь счетчик ссылок, равный единице. Пожалуйста, предоставьте код, не стесняйтесь игнорировать noexcept гарантии. - person Jonathan Wakely; 23.03.2015
comment
Просто собирая код, я обнаружил, что есть 129 basic_string функций-членов, а также бесплатные функции. Стоимость абстракции: этот импровизированный неоптимизированный свежий код нулевой версии на 50-100% медленнее как с g++, так и с MSVC. Он не обеспечивает потокобезопасности (я думаю, что достаточно просто использовать shared_ptr), и этого достаточно для поддержки сортировки словаря для целей синхронизации, но по модулю ошибок это доказывает, что ссылка с подсчетом basic_string разрешена, за исключением требований C++ noexcept . github.com/alfps/In-principle-demo-of -ref-counted-basic_string - person Cheers and hth. - Alf; 23.03.2015
comment
shared_ptr не поможет вам обойти проблему безопасности потоков в моем последнем комментарии: вам нужно иметь возможность вызывать data() одновременно в двух потоках, что означает выделение новой строки и замену имеющихся символов. shared_ptr не поддерживает одновременные обновления, только одновременные чтения. Так что вам все еще нужен мьютекс. Кроме того, если у вас есть const string, его член shared_ptr будет константой и в любом случае не может быть обновлен. Учитывая, что основной мотивацией для запрета строк COW была безопасность потоков, вам не следует пытаться прикручивать это впоследствии, вам нужно спроектировать это. - person Jonathan Wakely; 23.03.2015
comment
Хорошо, я вижу mutable на вашем shared_ptr члене. Однако это не помогает в гонках данных. Нет необходимости тратить время на поддержку распределителя, это ортогонально рассматриваемому вопросу. Аналогично назначить/заменить/найти и т. д. - person Jonathan Wakely; 23.03.2015
comment
Простая идея для потокобезопасности константных функций: скопировать shared_ptr в локальную в каждой такой функции, а затем использовать локальную. Я полагаю, что есть некоторые атомарные функции поддержки, которые могут выполнять такое копирование? Не использовал их. - person Cheers and hth. - Alf; 23.03.2015
comment
Да, вы можете использовать их для атомарного обновления shared_ptr, теперь все ваши строки совместно используют глобальный пул мьютексов, который вам нужно использовать при каждом вызове data() или operator[] или begin() даже для константной строки. Привет спор. И у вас все равно будет TOCTTOU гонка в Refcounted_string_::ensure_unique_buffer(). Возможно, вы сможете доказать, что строка C++ 11 COW возможна, если ваша реализация std::lib полностью разрушит производительность программы. Но в реальном мире это не вариант. - person Jonathan Wakely; 23.03.2015
comment
Также недостаточно использовать атомарные операции в каждой такой функции, это гонка данных, например. return p_buffer_->capacity(); в одном потоке, в то время как другой поток выполняет atomic_load(&p_buffer_), поэтому вам нужно использовать атомарные операции для каждого отдельного доступа к p_buffer_. - person Jonathan Wakely; 23.03.2015
comment
Представления, которые вы рисуете о мьютексах и т. д., слишком богаты (не уверен, что я достаточно хорошо об этом рассказал), но я думаю, можно с уверенностью сказать, что так же, как ограничения noexcept являются формальными барьерами для реализации с подсчетом ссылок, поэтому требования безопасности потоков — это практические барьеры, влияющие на производительность в определенных сценариях. Я думаю, может быть, мы можем договориться об этом. Затем остается интерпретация того, что OP означает COW, где я вижу разумный строковый тип с подсчетом ссылок, который только формально останавливается noexcept. - person Cheers and hth. - Alf; 23.03.2015
comment
влияние на производительность в определенных сценариях. РОФЛ. Хорошо, теперь попробуйте выполнить требования сложности в 21.4.5 [string.access]. noexcept проблема не в этом. Иногда ошибаться нормально, Альф. - person Jonathan Wakely; 23.03.2015
comment
Давайте продолжим обсуждение в чате. - person Jonathan Wakely; 23.03.2015
comment
Хорошее наблюдение, это новые требования С++ 11, и они не могут быть выполнены реализацией COW. Кроме noexcept не встречается. Насчет «Хорошо ошибаться», вы удалите этот ответ? Когда у меня будет время (извините, я уже слишком много использовал), я обновлю свой ответ обоими вашими наблюдениями: (1) С++ 03 сделал специальное исключение для первых вызовов, где, например. s[i] может сделать недействительным указатель, полученный от s.data(), даже несмотря на то, что в целом клиентский код не может знать, является ли вызов первым или нет, и (2) новые требования сложности O (1) к строковым операциям, где C ++ 03 не было . - person Cheers and hth. - Alf; 23.03.2015
comment
@JonathanWakely: я могу представить некоторые случаи, когда строка, которая использует общие резервные хранилища до тех пор, пока к ним не будет доступа, может быть полезна. Например, объединение двух строк с общими резервными хранилищами может дать новый строковый объект, содержащий ссылки на оба резервных хранилища; объединение этого с чем-то другим может создать дерево. Если промежуточные строки не используются ни для каких целей, кроме как для будущих конкатенаций, такой подход может показаться полезным. Это не было бы копированием при записи в обычном смысле, но идея совместного использования данных оставалась бы практичной. - person supercat; 12.11.2015
comment
@supercat, это добавит много накладных расходов, но приведет к пессимизации всех применений, не связанных с объединением строк. Также см. точку, которую я сделал в реализации коровьей стандартной строки в c11">stackoverflow.com/questions/12199710/ - person Jonathan Wakely; 13.11.2015
comment
−1 Ответы Dave S и gbjbaanb верны. является ложью. В частности, утверждение Дэйва иррационально, с языком, который выглядит и ощущается как логика, но таковым не является. Вывод в конце этого ответа о том, что COW с ошибками в libstdc++ g++ не разрешен, в порядке. - person Cheers and hth. - Alf; 29.07.2016

Да, CoW — приемлемый механизм для создания более быстрых строк... но...

это делает многопоточный код медленнее (все эти блокировки для проверки того, что вы единственный, кто пишет, убивает производительность при использовании большого количества строк). Это было основной причиной, по которой CoW был уничтожен много лет назад.

Другие причины заключаются в том, что оператор [] вернет вам строковые данные без какой-либо защиты для вас от перезаписи строки, которую кто-то другой считает неизменной. То же самое относится к c_str() и data().

Быстрый Google говорит, что многопоточность в основном является причиной, по которой она была фактически запрещена (не явно).

В предложении сказано:

Предложение

Мы предлагаем сделать все операции доступа к итераторам и элементам безопасными и одновременно выполняемыми.

Мы повышаем стабильность операций даже в последовательном коде.

Это изменение эффективно запрещает реализацию копирования при записи.

с последующим

Самая большая потенциальная потеря производительности из-за отказа от реализации копирования при записи — это повышенное потребление памяти для приложений с очень большими строками, предназначенными в основном для чтения. Однако мы считаем, что для этих приложений веревки являются лучшим техническим решением, и рекомендуем рассмотреть предложение о веревках для включения в библиотеку TR2.

Веревки являются частью STLPort и SGI STL.

person gbjbaanb    schedule 30.08.2012
comment
Проблема с оператором [] на самом деле не проблема. Константный вариант предлагает защиту, а неконстантный вариант всегда имеет возможность выполнить CoW в это время (или быть действительно сумасшедшим и настроить отказ страницы, чтобы вызвать его). - person Christopher Smith; 30.09.2014
comment
+1 Переход к проблемам. - person Cheers and hth. - Alf; 18.12.2014
comment
просто глупо, что класс std::cow_string не был включен, с lock_buffer() и т. д. много раз я знаю, что потоки не являются проблемой. на самом деле чаще, чем нет. - person Erik Aronesty; 16.04.2015
comment
Мне нравится предложение альтернативы, т.е. веревки. Интересно, есть ли какие-либо другие альтернативные типы и реализации. - person Voltaire; 09.05.2019

Из 21.4.2 конструкторы basic_string и операторы присваивания [string.cons]

basic_string(const basic_string<charT,traits,Allocator>& str);

[...]

2 Эффекты: создает объект класса basic_string, как указано в таблице 64. [...]

В таблице 64 полезно показано, что после построения объекта с помощью этого (копирующего) конструктора this->data() имеет значение:

указывает на первый элемент выделенной копии массива, на первый элемент которого указывает str.data()

Аналогичные требования предъявляются и к другим подобным конструкторам.

person Luc Danton    schedule 30.08.2012
comment
+1 Объясняет, как C++11 (хотя бы частично) запрещает COW. - person Cheers and hth. - Alf; 18.12.2014
comment
Извините, я устал. Это не объясняет ничего, кроме того, что вызов .data() должен инициировать копирование COW, если буфер в настоящее время используется совместно. Тем не менее, это полезная информация, поэтому я оставляю голосование за. - person Cheers and hth. - Alf; 18.12.2014

Поскольку теперь гарантируется, что строки хранятся непрерывно, и теперь вам разрешено брать указатель на внутреннее хранилище строки (т.е. &str[0] работает так же, как и для массива), невозможно сделать полезный COW выполнение. Вы должны были бы сделать копию для слишком многих вещей. Даже простое использование operator[] или begin() для неконстантной строки потребует копирования.

person Dirk Holsopple    schedule 30.08.2012
comment
Я думаю, что строки в С++ 11 гарантированно будут храниться непрерывно. - person mfontanini; 30.08.2012
comment
Раньше вам приходилось делать копии во всех этих ситуациях, и это не было проблемой... - person David Rodríguez - dribeas; 30.08.2012
comment
@mfontanini да, но раньше их не было - person Dirk Holsopple; 30.08.2012
comment
Хотя C++11 гарантирует непрерывность строк, это ортогонально запрету строк COW. Строка COW в GCC непрерывна, поэтому очевидно, что ваше утверждение о том, что невозможно создать полезную реализацию COW, является подделкой. - person Jonathan Wakely; 22.03.2015
comment
@JonathanWakely: если объединить две строки и запросить у полученной строки резервное хранилище, он должен сообщить о непрерывной последовательности символов. Существуют ли какие-либо требования к хранению строк, резервное хранилище которых никогда не было раскрыто? - person supercat; 12.11.2015
comment
@supercat, запрос резервного хранилища (например, путем вызова c_str()) должен быть O (1) и не может вызывать броски, а также не должен вводить гонки данных, поэтому очень сложно выполнить эти требования, если вы лениво объединяете. На практике единственный разумный вариант — всегда хранить непрерывные данные. - person Jonathan Wakely; 13.11.2015

Запрещен ли COW basic_string в C++11 и более поздних версиях?

Касательно

Правильно ли я понимаю, что C++11 не поддерживает реализацию std::string на основе COW?

Да.

Касательно

Если да, указано ли это ограничение явно где-то в новом стандарте (где)?

Почти напрямую, по требованиям постоянной сложности для ряда операций, которые потребуют O(n) физического копирования строковых данных в COW выполнение.

Например, для функций-членов

auto operator[](size_type pos) const -> const_reference;
auto operator[](size_type pos) -> reference;

что в реализации COW ¹оба инициируют копирование строковых данных, чтобы отменить совместное использование строкового значения, стандарт C++11 требует

C++11 §21.4.5/4:

Сложность: постоянное время.

что исключает такое копирование данных и, следовательно, COW.

C++03 поддерживал реализации COW, отсутствие этих требований к постоянной сложности и, при определенных ограничительных условиях, разрешая вызовы operator[](), at(), begin(), rbegin(), end() или rend() для аннулирования ссылок, указателей. итераторы, относящиеся к элементам строки, т.е. для возможного копирования данных COW. Эта поддержка была удалена в C++11.


Запрещен ли COW также правилами аннулирования C++11?

В другом ответе, который на момент написания был выбран в качестве решения, за который активно проголосовали и, следовательно, по-видимому, ему поверили, утверждается, что

Для строки COW вызов non-const operator[] потребует создания копии (и аннулирования ссылок), что запрещено абзацем [в кавычках] выше [C++11 §21.4.1/6]. . Следовательно, в С++ 11 больше не разрешено иметь строку COW.

Это утверждение неверно и вводит в заблуждение по двум основным причинам:

  • Это неверно указывает, что только методы доступа к элементам, отличные от const, должны инициировать копирование данных COW.
    Но также методы доступа к элементам const должны запускать копирование данных, поскольку они позволяют клиентскому коду формировать ссылки или указатели, которые (в C++11) не разрешается позже аннулировать с помощью операций, которые могут инициировать копирование данных COW.
  • Ошибочно предполагается, что копирование данных COW может привести к аннулированию ссылки.
    Но в правильной реализации копирование данных COW, отменяющее совместное использование строкового значения, выполняется до того, как появятся какие-либо ссылки, которые могут быть признан недействительным.

Чтобы увидеть, как будет работать правильная реализация C++11 COW для basic_string, когда игнорируются требования O(1), делающие это недействительным, подумайте о реализации, в которой строка может переключаться между политиками владения. Экземпляр строки начинается с политики Sharable. Если эта политика активна, ссылки на внешние элементы быть не могут. Экземпляр может перейти к уникальной политике, и он должен сделать это, когда потенциально создается ссылка на элемент, например, при вызове .c_str() (по крайней мере, если это создает указатель на внутренний буфер). В общем случае, когда несколько экземпляров совместно владеют значением, это влечет за собой копирование строковых данных. После этого перехода к уникальной политике экземпляр может вернуться обратно к совместно используемому только с помощью операции, которая делает недействительными все ссылки, например назначение.

Таким образом, хотя вывод этого ответа о том, что строки COW исключены, является правильным, предлагаемое рассуждение неверно и сильно вводит в заблуждение.

Я подозреваю, что причиной этого недоразумения является ненормативное примечание в приложении C C++11:

C++11 §C.2.11 [diff.cpp03.strings], about §21.3:

Изменение: требования basic_string больше не допускают использование строк с подсчетом ссылок
Обоснование: недействительность строк с подсчетом ссылок немного отличается. Это изменение упорядочивает поведение (так в оригинале) для этого международного стандарта.
Влияние на исходную функцию: Действительный код C++ 2003 может выполняться по-другому в этом международном стандарте.

Здесь обоснование объясняет основную почему было решено удалить специальную поддержку COW C++03. Это обоснование, почему, не является тем, как стандарт эффективно запрещает внедрение COW. Стандарт запрещает COW через требования O(1).

Короче говоря, правила аннулирования C++11 не исключают COW-реализации std::basic_string. Но они исключают достаточно эффективную неограниченную реализацию COW в стиле C++03, подобную той, что используется по крайней мере в одной из реализаций стандартной библиотеки g++. Специальная поддержка C++03 COW обеспечила практическую эффективность, в частности использование методов доступа к элементам const, за счет тонких, сложных правил аннулирования:

C++03 §21.3/5 which includes “first call” COW support:

Ссылки, указатели и итераторы, относящиеся к элементам последовательности basic_string, могут стать недействительными при следующих случаях использования этого объекта basic_string:
— в качестве аргумента для функций, не являющихся членами swap() (21.3.7.8). ), operator>>() (21.3.7.9) и getline() (21.3.7.9).
— В качестве аргумента для basic_string::swap().
— Вызов функций-членов data() и c_str().
— Вызов функций-членов, не являющихся const, кроме operator[](), at(), begin(), rbegin(), end() и rend().
— после любого из вышеперечисленных применений, кроме форм insert() и erase(), которые возвращают итераторы, первый вызов не-36_ функций-членов operator[](), at(), begin(), rbegin(), end() или rend().

Эти правила настолько сложны и изощренны, что я сомневаюсь, что многие программисты, если вообще найдутся, смогут дать точную сводку. Я не могу.


Что делать, если требования O(1) игнорируются?

Если требования постоянного времени С++ 11, например. operator[] игнорируются, то COW для basic_string может быть технически осуществимым, но сложным в реализации.

Операции, которые могут получить доступ к содержимому строки без копирования данных COW, включают:

  • Объединение через +.
  • Выход через <<.
  • Использование basic_string в качестве аргумента для функций стандартной библиотеки.

Последнее потому, что стандартной библиотеке разрешено полагаться на знания и конструкции, специфичные для реализации.

Кроме того, реализация может предлагать различные нестандартные функции для доступа к содержимому строки без запуска копирования данных COW.

Основным усложняющим фактором является то, что в С++ 11 basic_string доступ к элементу должен инициировать копирование данных (отмена совместного использования строковых данных), но требуется не бросать, например. C++11 §21.4.5/3 Выдает: Ничего.. Таким образом, он не может использовать обычное динамическое выделение для создания нового буфера для копирования данных COW. Один из способов обойти это — использовать специальную кучу, в которой можно зарезервировать память без ее фактического выделения, а затем резервировать необходимый объем для каждой логической ссылки на строковое значение. Резервирование и разрезервирование в такой куче может быть постоянным временем, O(1), а выделение суммы, которая уже зарезервирована, может быть noexcept. Чтобы соответствовать требованиям стандарта, при таком подходе кажется, что должна быть одна такая специальная куча на основе резервирования для каждого отдельного распределителя.


Примечания.
¹ Метод доступа к элементу const инициирует копирование данных COW, поскольку он позволяет клиентскому коду получить ссылку или указатель на данные, которые нельзя аннулировать более поздним копирование данных, вызванное, например, метод доступа к элементу, отличный от const.

person Cheers and hth. - Alf    schedule 29.07.2016
comment
Но в правильной реализации копирование данных COW, не совместное использование строкового значения, выполняется до того, как появятся какие-либо ссылки, которые могут быть признаны недействительными. Пример в моем ответе противоречит этому, показывая, как ссылка до того, как произойдет какой-либо обмен, дает вам ссылку, которая впоследствии может стать недействительной. Вы не можете отменить совместный доступ к строке, которой еще не предоставили общий доступ. Если вы собираетесь отрицать другие ответы, потому что вы не согласны с деталями, вам нужно правильно понять свои особенности. - person Jonathan Wakely; 29.07.2016
comment
@JonathanWakely: Ваш пример — хороший пример неправильной для C++11 реализации. Возможно, это было правильно для C++03. Но этот вопрос SO касается С++ 11 и более поздних версий. Если вы собираетесь отрицать другие ответы, потому что вы не согласны с деталями, вам нужно правильно понять свои особенности. - person Cheers and hth. - Alf; 29.07.2016
comment
Ваш пример — хороший пример реализации, некорректной для C++11. Возможно, это было правильно для C++03. Да, в этом суть примера. Он показывает строку COW, которая была разрешена в C++03, поскольку не нарушает старые правила аннулирования итератора, и недопустима в C++11, поскольку нарушает новые правила аннулирования итератора. И это также противоречит утверждению, которое я процитировал в комментарии выше. - person Jonathan Wakely; 29.07.2016
comment
@JonathanWakely: Нет, ваш пример неправильной реализации ничему не противоречит. Это то же самое, что и при неправильной попытке открыть дверь, т.е. нюхает петли. Тот факт, что этот неверный подход не открывает дверь, не имеет никакого отношения к тому, можно ли открыть дверь: это просто глупо. - person Cheers and hth. - Alf; 29.07.2016
comment
@Jonathan: Если вы не видите, как будет работать правильная реализация (по модулю игнорирования требований O (1), то есть правильная, кроме этого), когда s.c_str() вызывается, s становится неразделяемым, если он был общим / доступным. - person Cheers and hth. - Alf; 29.07.2016
comment
Что делать, если s еще не используется совместно, когда вы вызываете s.c_str()? Как вы справляетесь с позже аннулированием указателя, возвращаемого c_str()? Строка должна помнить, когда-либо использовалась ли какая-либо ссылка, даже если она была уникальным владельцем, а затем никогда не разрешать совместное использование. Это непрактично в реальном мире. - person Jonathan Wakely; 29.07.2016
comment
@Jonathan: строка COW изначально является общей. Это означает, что он действует так, как если бы у него было отдельное значение со счетчиком ссылок, например shared_ptr. - person Cheers and hth. - Alf; 29.07.2016
comment
Нет, изначально он принадлежит только одному человеку. Это не разделяется. Он становится общим, когда счетчик ссылок превышает единицу. Если вы собираетесь переопределить значение COW, отличное от сути вопроса, то сделайте это в другом месте. - person Jonathan Wakely; 29.07.2016
comment
Предполагая неправильную реализацию, как в случае с No, она изначально принадлежит уникальному владельцу, никуда не ведет: это круговая логика. Или хуже. Мое нюхание не открывает дверь, ее нельзя открыть. Что, использовать ручку? Нет, правильное открывание двери, как я это определяю, осуществляется исключительно с помощью обнюхивания петель. - person Cheers and hth. - Alf; 29.07.2016
comment
@JonathanWakely: Опять же, если предположить, что вы имеете в виду то, что пишете, и просто не вникаете в суть вещей, подумайте о реализации, в которой строка может переключаться между политиками владения. Строка COW начинается с политики Sharable. Он может перейти к уникальной политике и должен сделать это, когда потенциально создается ссылка на элемент, например, при вызове .c_str() (по крайней мере, если это создает указатель на внутренний буфер). Обычно это влечет за собой копирование строковых данных. После этого он может перейти в Sharable только с помощью операции, которая аннулирует все ссылки, например. назначение. Это ясно? - person Cheers and hth. - Alf; 29.07.2016
comment
Если бы вы сказали общий доступ, а не изначально общий доступ, я бы не стал спорить. Сказать, что что-то изначально совместно используется, просто сбивает с толку. Поделился с собой? Это не то, что означает это слово. Но я повторяю: ваша попытка доказать, что правила аннулирования итератора C++11 не запрещают какую-то гипотетическую строку COW, которая никогда не использовалась на практике (и имела бы неприемлемую производительность), когда они определенно запрещают вид строки COW, которая использовалась на практике, несколько академична и бессмысленна. - person Jonathan Wakely; 29.07.2016
comment
@JonathanWakely: Нет, вопрос о том, что разрешает С++ 11, касается не старых строк C++ 03 COW g++. Это просто неправильное направление от вас. Вопрос в том, что позволяет С++ 11 или нет. И С++ 11 не разрешает COW-реализации basic_string. Правильно ли отрицать ответ, который правильно понимает этот основной факт, когда в нем есть другие биты неправильно? Да, минусую за некорректность, когда человек отказывается что-то исправлять. - person Cheers and hth. - Alf; 29.07.2016
comment
Предложенная вами строка COW интересна, но я не уверен, насколько полезной она будет. Суть строки COW состоит в том, чтобы копировать строковые данные только в том случае, если записываются две строки. Предлагаемая вами реализация требует копирования, когда происходит любая определяемая пользователем операция чтения. Даже если компилятор знает, что это только чтение, он все равно должен копировать. Кроме того, копирование уникальной строки приведет к копированию ее строковых данных (предположительно в состояние Sharable), что снова делает COW довольно бессмысленным. Таким образом, без гарантий сложности вы могли бы написать... действительно паршивую строку COW. - person Nicol Bolas; 29.07.2016
comment
Таким образом, хотя вы технически правы в том, что гарантии сложности — это то, что мешает вам написать любую форму COW, на самом деле именно [basic.string]/5 мешает вам написать какую-либо действительно полезную< /i> форма строки COW. - person Nicol Bolas; 29.07.2016
comment
требует копирования, когда происходит любая определяемая пользователем операция чтения, нет, это неправильно. - person Cheers and hth. - Alf; 29.07.2016
comment
это действительно [basic.string]/5, который не позволяет вам написать какую-либо действительно полезную форму строки COW, к счастью, вопрос был не о субъективном мнении о полезности :-), а о том, что абсолютно разрешено или нет. - person Cheers and hth. - Alf; 29.07.2016
comment
ОП сказал: Насколько я понимаю, копирование при записи не является жизнеспособным способом реализовать соответствующий std::string в C++11. Неприемлемая производительность не будет жизнеспособным способом реализации соответствующего std::string. Мои пользователи не приняли бы его, и они бы не приняли его, если бы я сказал им, что их субъективное мнение о его полезности не имеет значения. - person Jonathan Wakely; 03.08.2016
comment
@JonathanWakely: (1) Вопрос не в вашей цитате. Вот вопрос: «Правильно ли я понимаю, что C++11 не допускает реализацию std::string на основе COW? Если да, указано ли это ограничение явно где-то в новом стандарте (где)?» (2) Ваше мнение о том, что COW std::string при игнорировании требований O(1) будет неэффективным, является вашим мнением. Я не знаю, что это может быть за представление, но я думаю, что это утверждение выдвигается больше из-за ощущения, из-за вибраций, которые оно передает, чем из-за какого-либо отношения к этому ответу. - person Cheers and hth. - Alf; 03.08.2016

Меня всегда интересовали неизменяемые коровы: как только корова создана, меня можно изменить только путем присвоения от другой коровы, следовательно, она будет соответствовать стандарту.

У меня было время попробовать это сегодня для простого сравнительного теста: карта размера N с ключом строка/корова, где каждый узел содержит набор всех строк на карте (у нас есть NxN количество объектов).

Со строками размером ~300 байт и N=2000 коровы работают немного быстрее и используют почти на порядок меньше памяти. См. ниже, размеры указаны в тысячах фунтов, прогон b — с коровами.

~/icow$ ./tst 2000
preparation a
run
done a: time-delta=6 mem-delta=1563276
preparation b
run
done a: time-delta=3 mem-delta=186384
person zzz777    schedule 16.10.2019