Я исходил из того, что копирование при записи не является жизнеспособным способом реализации согласования std::string
в C ++ 11, но когда недавно это обсуждалось, я обнаружил, что не могу напрямую поддержать это утверждение.
Правильно ли я, что C ++ 11 не допускает реализации на основе COW std::string
?
Если да, то указано ли это ограничение явно где-нибудь в новом стандарте (где)?
Или это ограничение подразумевается в том смысле, что это совокупный эффект новых требований std::string
, препятствующих реализации на основе COW std::string
. В этом случае мне было бы интересно узнать о том, как «C ++ 11 эффективно запрещает std::string
реализации на основе COW ».
Ответы:
Это не разрешено, потому что согласно стандарту 21.4.1 p6 недействительность итераторов / ссылок разрешена только для
Для строки COW вызов неконстантной функции
operator[]
потребует создания копии (и признания недействительными ссылок), что запрещено в параграфе выше. Следовательно, больше не разрешено иметь строку COW в C ++ 11.источник
std::string a("something"); char& c1 = a[0]; std::string b(a); char& c2 = a[1];
c1 - это ссылка на a. Затем вы «копируете» файл. Затем, когда вы пытаетесь взять ссылку во второй раз, он должен сделать копию, чтобы получить неконстантную ссылку, поскольку есть две строки, которые указывают на один и тот же буфер. Это должно сделать недействительной первую взятую ссылку и противоречит приведенному выше разделу.operator[]
(1) должен делать копию и что (2) это незаконно. С каким из этих двух пунктов вы не согласны? Глядя на ваш первый комментарий, кажется, что реализация может совместно использовать строку, по крайней мере, в соответствии с этим требованием, до момента обращения к ней, но что при доступе как для чтения, так и для записи необходимо будет отменить ее. Это ваше рассуждение?Ответы на Dave S и gbjbaanb являются правильными . (И Люк Дантон тоже прав, хотя это скорее побочный эффект запрета строк COW, а не исходное правило, запрещающее это.)
Но чтобы прояснить некоторую путаницу, я собираюсь добавить несколько дополнительных пояснений. Различные комментарии ссылаются на мой комментарий к bugzilla GCC, в котором приводится следующий пример:
Смысл этого примера - продемонстрировать, почему строка подсчета ссылок (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 в libstdС ++, поэтому такая строка COW не разрешена в C ++ 11.
источник
.data()
(и при каждом возврате указателя, ссылки или итератора), не страдает от этой проблемы. Т.е. (инвариантный) буфер в любой момент не используется совместно, либо используется совместно без внешних ссылок. Я думал, что вы задумали комментарий об этом примере как неформальный отчет об ошибке в виде комментария, очень извиняюсь за недопонимание! Но, как вы можете видеть, рассматривая описанную здесь реализацию, которая отлично работает в C ++ 11, когдаnoexcept
требования игнорируются, в примере ничего не говорится о формальном. Я могу предоставить код, если хотите.std::string
, и я искренне сомневаюсь, что вы сможете продемонстрировать полезную, производительную строку COW, которая соответствует требованиям C ++ 11 к недействительности. Поэтому я утверждаю, чтоnoexcept
спецификации, которые были добавлены в последнюю минуту, являются следствием запрета строк COW, а не основной причиной. N2668 кажется совершенно ясным, почему вы продолжаете отрицать явные доказательства намерений комитета, изложенные там?data()
это константная функция-член, поэтому ее следует безопасно вызывать одновременно с другими константными членами и, например, вызыватьdata()
одновременно с другим потоком, создающим копию строки. Таким образом, вам понадобятся все накладные расходы на мьютекс для каждой строковой операции, даже константной, или сложность изменяемой структуры с подсчетом ссылок без блокировок, и, в конце концов, вы получите общий доступ только в том случае, если вы никогда не изменяете или не получаете доступа ваши строки, так много-много строк будут иметь счетчик ссылок, равный единице. Пожалуйста, предоставьте код, не стесняйтесь игнорироватьnoexcept
гарантии.basic_string
функций-членов плюс бесплатные функции. Стоимость абстракции: этот нестандартный неоптимизированный свежий код нулевой версии на 50–100% медленнее как с g ++, так и с MSVC. Он не обеспечивает потокобезопасность (shared_ptr
я думаю, достаточно легко использовать ), и этого достаточно для поддержки сортировки словаря для целей синхронизации, но по модулю ошибок он доказывает, что подсчет ссылокbasic_string
разрешен, за исключениемnoexcept
требований C ++ . github.com/alfps/In-principle-demo-of-ref-counted-basic_stringДа, CoW - приемлемый механизм для создания более быстрых струн ... но ...
он замедляет работу многопоточного кода (вся эта блокировка для проверки того, что вы пишете только вы, убивает производительность при использовании большого количества строк). Это была основная причина, по которой CoW был убит много лет назад.
Другая причина заключается в том, что
[]
оператор вернет вам строковые данные без какой-либо защиты для вас от перезаписи строки, которую кто-то ожидает неизменной. То же касаетсяc_str()
иdata()
.Quick google говорит, что многопоточность в основном является причиной того, что она была фактически запрещена (не явно).
В предложении говорится:
с последующим
Канаты являются частью STLPort и SGIs STL.
источник
Из 21.4.2 конструкторы и операторы присваивания basic_string [string.cons]
В таблице 64 показано, что после создания объекта с помощью этого (копирующего) конструктора
this->data()
значение as:Аналогичные требования предъявляются и к другим аналогичным конструкторам.
источник
Поскольку теперь гарантируется, что строки хранятся непрерывно, и теперь вам разрешено использовать указатель на внутреннее хранилище строки (т.е. & str [0] работает так же, как и для массива), невозможно создать полезную COW реализация. Вам придется делать копии слишком многих вещей. Даже простое использование
operator[]
илиbegin()
для неконстантной строки потребует копии.источник
c_str()
) должен быть O (1) и не может отбрасывать, и не должен вводить гонки данных, поэтому очень сложно удовлетворить эти требования, если вы лениво объединяете. На практике единственный разумный вариант - всегда хранить непрерывные данные.basic_string
Запрещена ли COW в C ++ 11 и новее?относительно
Да.
относительно
Практически напрямую, по требованиям постоянной сложности для ряда операций, которые потребовали бы O ( n ) физического копирования строковых данных в реализации COW.
Например, для функций-членов
… Который в реализации COW будет «запускать копирование строковых данных для отмены совместного использования строкового значения», стандарт C ++ 11 требует
С ++ 11 §21.4.5 / 4 :… Что исключает такое копирование данных и, следовательно, COW.
C ++ 03 поддерживаются реализации коровы не имея эти требования постоянной сложности, и, при определенных ограничительных условиях, что позволяет звонить на
operator[]()
,at()
,begin()
,rbegin()
,end()
, илиrend()
о признании недействительных ссылок, указатели и итераторах со ссылкой на строковых элементы, т.е. , возможно , понесет Копирование данных COW. Эта поддержка была удалена в C ++ 11.Является ли COW также запрещенным правилами аннулирования C ++ 11?
В другом ответе, который на момент написания был выбран в качестве решения и который получил широкое одобрение и, следовательно, очевидно, что ему верили, утверждается, что
Это утверждение неверно и вводит в заблуждение по двум основным причинам:
const
доступа, не относящиеся к элементам, должны запускать копирование данных COW.Но также средства
const
доступа к элементам должны запускать копирование данных, потому что они позволяют клиентскому коду формировать ссылки или указатели, которые (в C ++ 11) не разрешено аннулировать позже с помощью операций, которые могут запускать копирование данных COW.Но в правильной реализации копирование данных COW без совместного использования строкового значения выполняется до того, как появятся какие-либо ссылки, которые могут быть признаны недействительными.
Чтобы увидеть, как
basic_string
будет работать правильная реализация C ++ 11 COW , когда игнорируются требования O (1), которые делают это недействительным, подумайте о реализации, в которой строка может переключаться между политиками владения. Экземпляр строки начинается с политики Sharable. Если эта политика активна, ссылки на внешние элементы быть не могут. Экземпляр может перейти к уникальной политике, и он должен сделать это, когда потенциально создается ссылка на элемент, например, с вызовом.c_str()
(по крайней мере, если это создает указатель на внутренний буфер). В общем случае, когда несколько экземпляров совместно владеют значением, это влечет за собой копирование строковых данных. После этого перехода к политике уникальности экземпляр может вернуться к общему доступу только с помощью операции, которая делает недействительными все ссылки, например назначение.Таким образом, хотя вывод из этого ответа о том, что строки COW исключены, верен, предлагаемая аргументация неверна и сильно вводит в заблуждение.
Я подозреваю, что причиной этого недоразумения является ненормативное примечание в приложении C C ++ 11:
C ++ 11 §C.2.11 [diff.cpp03.strings], о §21.3:Здесь обоснование объясняет, почему было решено удалить специальную поддержку COW в C ++ 03. Это объяснение, причина , не в том, что стандарт эффективно запрещает реализацию COW. Стандарт запрещает COW через требования O (1).
Короче говоря, правила аннулирования C ++ 11 не исключают реализации COW
C ++ 03 §21.3 / 5, который включает поддержку COW «первый звонок»:std::basic_string
. Но они исключают достаточно эффективную неограниченную реализацию COW в стиле C ++ 03, подобную той, которая есть по крайней мере в одной из реализаций стандартной библиотеки g ++. Специальная поддержка C ++ 03 COW обеспечила практическую эффективность, в частности, использование средствconst
доступа к элементам, за счет тонких, сложных правил аннулирования:Эти правила настолько сложны и тонки, что я сомневаюсь, что многие программисты, если таковые имеются, могут дать точное резюме. Я не мог.
Что делать, если требования O (1) игнорируются?
Если требования C ++ 11 к постоянному времени, например,
operator[]
не учитываются, то COW forbasic_string
может быть технически осуществимым, но трудным для реализации.Операции, которые могут получить доступ к содержимому строки без копирования данных COW, включают:
+
.<<
.basic_string
качестве аргумента стандартных библиотечных функций.Последнее, потому что стандартной библиотеке разрешено полагаться на конкретные знания и конструкции реализации.
Кроме того, реализация может предлагать различные нестандартные функции для доступа к содержимому строки без запуска копирования данных COW.
Основным усложняющим фактором является то, что в C ++ 11
basic_string
доступ к элементу должен запускать копирование данных (не разделять строковые данные), но не должен бросать , например, C ++ 11 §21.4.5 / 3 «Выбрасывает : ничего». И поэтому он не может использовать обычное динамическое размещение для создания нового буфера для копирования данных COW. Один из способов обойти это - использовать специальную кучу, в которой память может быть зарезервирована без фактического выделения, а затем зарезервировать необходимое количество для каждой логической ссылки на строковое значение. Резервирование и снятие резервирования в такой куче может быть постоянным временем, O (1), а выделение суммы, которая уже зарезервирована, может бытьnoexcept
, Чтобы соответствовать требованиям стандарта, при таком подходе кажется, что потребуется одна такая специальная куча на основе резервирования для каждого отдельного распределителя.Примечания:
¹ Средство
const
доступа к элементам запускает копирование данных COW, потому что оно позволяет клиентскому коду получить ссылку или указатель на данные, которые не разрешается аннулировать путем последующего копирования данных, инициированного, например, средствомconst
доступа, не являющимся элементом.источник
std::string
при игнорировании требований O (1) будет неэффективным, - это ваше мнение. Я не знаю, каким может быть выступление, но я думаю, что это утверждение выдвигается скорее из-за ощущения его, из-за вибрации, которую оно передает, чем из-за какого-либо отношения к этому ответу.Меня всегда интересовали неизменяемые коровы: как только корова была создана, меня можно было изменить только через назначение другой коровы, следовательно, она будет соответствовать стандарту.
У меня было время попробовать его сегодня для простого сравнительного теста: карта размера N с ключом строка / корова, где каждый узел содержит набор всех строк на карте (у нас есть количество объектов NxN).
Со строками размером ~ 300 байт и N = 2000 коровы работают немного быстрее и используют почти на порядок меньше памяти. См. Ниже, размеры указаны в килобайтах, пробег с коровами.
источник