Как реализовать строку, которая выделяется исключительно в стеке

Около десяти лет назад в одном из проектов мы обнаружили, что динамическое распределение std::vector вызывало серьезную потерю производительности. В этом случае было выделено много небольших векторов, поэтому быстрым решением было написать векторный класс, обернутый вокруг предварительно выделенного массива char на основе стека, используемого в качестве необработанного хранилища для его емкости. Результат был static_vector<typename T, std::size_t Max>. Такую вещь достаточно легко написать, если вы знаете некоторые основы, и вы можете найти немало таких чудовищ в сети < / а>. Фактически, у есть одно повышение, тоже сейчас.

Сейчас, работая над встроенной платформой, нам понадобится static_basic_string. Это будет строка, которая предварительно выделяет фиксированный максимальный объем памяти в стеке и использует его в качестве своей емкости.

Сначала я подумал, что это должно быть довольно просто (в конце концов, это могло быть основано на существующем static_vector), но, снова взглянув на интерфейс std::basic_string, я уже не в этом уверен. Это намного сложнее, чем интерфейс std::vector. В частности, реализация семейства find() функций, поставляемых с std::basic_string, - это больше, чем просто утомительная задача.

Это заставило меня снова задуматься. В конце концов, это то, для чего были созданы распределители: заменить распределение, основанное на new и delete, другими средствами. Однако сказать, что интерфейс распределителя является громоздким, было бы преуменьшением. Есть несколько статей, объясняющих это, но есть причина, по которой я видел так очень мало самодельных распределителей памяти за последние 15 лет.

Вот мой вопрос:

Если бы вам пришлось реализовать basic_string двойника, как бы вы это сделали?

  • Напишите свой static_basic_string?
  • Написать распределитель для перехода к std::basic_string?
  • Сделать то, о чем я не подумал?
  • Использовать что-то из Boost, о котором я не знаю?

Как всегда, для нас существует довольно существенное ограничение: будучи на встроенной платформе, мы привязаны к GCC 4.1.2, поэтому можем использовать только C ++ 03, TR1 и boost 1.52.


person sbi    schedule 14.10.2014    source источник
comment
Это может быть лучше подходит для programmers.se, поскольку он полностью находится на этапе проектирования.   -  person Marco A.    schedule 14.10.2014
comment
@ Марко: Ммм. Если посмотреть на programmers.stackexchange.com/help/on-topic, архитектура и дизайн программного обеспечения действительно перечислены там. Тем не мение. 1) Он похоронен среди множества других тем, которые не имеют ничего общего с моим вопросом и, кажется, указывают на то, что они говорят об архитектуре на совершенно другом уровне, и 2) Я не считаю это чисто дизайнерским вопросом, потому что интерфейс фиксированный (это интерфейс std::basic_string), и меня больше интересует, как его реализовать. Да, это нечетко, но я считаю, что вопрос здесь больше, чем там. ICBWT.   -  person sbi    schedule 14.10.2014
comment
Как вы думаете, почему распределители такие пугающие? .. Я использовал их пару раз; в конце концов, они для этого. Получите stack_string из basic_string, включите распределитель со встроенным буфером в качестве члена, передайте этот распределитель в ctor basic_string.   -  person ArunasR    schedule 14.10.2014
comment
Не может ли вообще быть и речи о динамическом распределении, или было бы приемлемо сделать одно динамическое выделение для каждого std::basic_string экземпляра, независимо от того, как его содержимое изменится позже?   -  person Angew is no longer proud of SO    schedule 14.10.2014
comment
@arunasr Я думаю, вам следует подробнее остановиться на этом и превратить его в ответ (возможно, с закрытым наследованием и несколькими объявлениями using).   -  person Angew is no longer proud of SO    schedule 14.10.2014
comment
Здесь можно использовать специальные распределители. Однако вам следует взглянуть на проблему с более высокого уровня: проанализировать требования к памяти и поток данных всего приложения. Профилируйте его и получите данные. Затем найдите стратегическую точку, где вы, возможно, выиграете от распределителя арены, который экономит оперативную память и время. В противном случае вы можете реализовать распределитель, который изначально выделяет из стека и, когда он превышает диапазон, повторно выделяет память в куче. См. Также: stackoverflow.com/questions/11648202/   -  person CouchDeveloper    schedule 14.10.2014
comment
Для меня немыслимо, что вы можете использовать boost в своем проекте и в то же время иметь проблемы с распределением памяти.   -  person user1095108    schedule 14.10.2014
comment
Я до сих пор не могу представить, что это чистый плюс для std::string с большой начальной емкостью и некоторым ограничением на сайте вызова на максимальный размер строки ?! Стоимость одного динамического распределения действительно хуже, чем этот уровень сложности и несомненно хрупкий код, который следует за ним? Я предполагаю, что если вы создаете метрический фрагмент этих вещей, но вместо этого пытаетесь использовать их повторно? Дополнительная информация о варианте использования и исходных проблемах, с которыми вы столкнулись, может помочь нам предоставить каноническое решение, которое не ограничивается NIHing.   -  person Lightness Races in Orbit    schedule 14.10.2014
comment
Связанный: stackoverflow.com/questions/783944/   -  person rubenvb    schedule 14.10.2014
comment
@ user1095108 непостижимо ... используйте ускорение ... и ... возникли проблемы с распределением памяти - не хотите ли сказать что-нибудь не совсем расплывчатое?   -  person Tony Delroy    schedule 14.10.2014
comment
@TonyD с использованием boost обычно означает огромные двоичные файлы и много выделений из кучи, по моему опыту, и то, и другое нежелательно на встроенных устройствах.   -  person user1095108    schedule 14.10.2014
comment
@ user1095108 IMHO, учитывая, насколько многочисленны и чрезвычайно разнообразны библиотеки boost, и что многие из них являются шаблонами только для заголовков с небольшими накладными расходами, связанными с их фактическим использованием, это удивительно дикая обобщение. ИТ-эквивалент любого, кто ест салаты, получает слишком много витамина А.   -  person Tony Delroy    schedule 14.10.2014
comment
@CouchDeveloper / @LightnessRacesinOrbit: рассмотрите struct с большим количеством мелких строковых членов - имея их почти непрерывно в памяти - обычно на одной (ах) странице (ах) кеша - звучит очень выгодно по сравнению с хранением каждого указателями, которые - даже если из один и тот же настраиваемый пул - может быть непрактично, чтобы избежать несмежного распределения без других нежелательных накладных расходов на координацию.   -  person Tony Delroy    schedule 14.10.2014
comment
@CouchDeveloper: Основная проблема здесь в том, что строка никогда не должна перераспределяться. Причины этого связаны с зависящим от платформы интерфейсом, но будьте уверены, нам нужны строки, которые никогда не перераспределяются, чтобы удовлетворить нашему критерию. Конечно, наличие фрагментации памяти на платформе с довольно ограниченной памятью плюс необходимость иметь дело с большим количеством (сотнями) таких строк также делает ее целью оптимизации. Но главная проблема в том, что в этой настройке не должно происходить перераспределения.   -  person sbi    schedule 14.10.2014
comment
@Lightness: см. Мой комментарий выше. Перераспределения необходимо категорически предотвратить. Текущий обходной путь - зарезервировать вдвое большую длину, которая нам нужна, но если это не удастся, мы получим сбои. Чтобы исключить, что сбои вызваны этим, мне поставили задачу устранить эту проблему.   -  person sbi    schedule 14.10.2014
comment
@TonyD Вы вообще не обратились к огромной проблеме с двоичными файлами, но назвали мое утверждение диким. Внутри boost много распределений, но я не собираюсь их подробно анализировать.   -  person user1095108    schedule 14.10.2014
comment
@ user1095108: Без обид, но эти обвинения безумны. Мы работаем на встроенной платформе и широко используем boost и более шаблонные части стандартной библиотеки, а также пишем наш собственный насыщенный шаблонами код. (Да, иногда кодовый двоичный объект из шаблонов является проблемой. Удивительно, но мы обнаружили, что включение встраивания уменьшило кодовый двоичный объект в достаточной степени, чтобы в настоящее время больше не быть головной болью.) Вызваны ли проблемы из-за ускорения или из-за кода из стандартная библиотека или наши собственные шаблоны просто не имеют значения. Проблема с платформой, а не с наддувом.   -  person sbi    schedule 14.10.2014
comment
Можете ли вы привести реальный пример использования, вызывающего проблему? Повторно используя объекты по-другому, вы максимально решите проблему.   -  person Neil Kirk    schedule 14.10.2014
comment
Какой пример функции отображается в вашем профилировщике?   -  person Neil Kirk    schedule 14.10.2014
comment
@Neil: прочтите этот комментарий.   -  person sbi    schedule 14.10.2014


Ответы (7)


Первый вопрос: сколько дополнительного интерфейса вы используете? Большинство дополнительных интерфейсов std::string могут быть тривиально реализованы с использованием функций в <algorithm> (например, std::find, std::find_if и std::search), и во многих случаях есть большие их части, которые в любом случае не будут использоваться. Просто реализуйте его по мере необходимости.

Я не думаю, что вы сможете заставить это работать с помощью специального распределителя. Единственный способ получить память «в стеке» - это объявить ее членом настраиваемого распределителя, что может создать всевозможные проблемы при их копировании. И распределители должны быть копируемыми, а копии должны быть идемпотентными.

Возможно, вы найдете бесплатную реализацию в сети std::string, в которой используется реализация небольшой строки; затем измените его так, чтобы размер отсечения (за пределами которого используется динамическое распределение) был больше, чем любые строки, которые вы фактически используете. (Доступно несколько реализаций стандартной библиотеки с открытым исходным кодом; версия, поставляемая с g ++, по-прежнему использует COW, но я подозреваю, что большинство других используют SSO.)

person James Kanze    schedule 14.10.2014
comment
Что касается настраиваемого распределителя памяти: он написал, что хочет обернуть существующий буфер, выделенный стеком, что должно быть возможно, верно? - person leemes; 14.10.2014
comment
@leemes Может быть. По-прежнему существует проблема области видимости: когда он возвращает строку, копия будет использовать тот же распределитель, что и копируемый. Если он, в свою очередь, использует буфер, выделенный стеком, у него будут большие проблемы. - person James Kanze; 14.10.2014
comment
Фактически, я понятия не имею, как заставить распределители использовать пространство локального стека, но мне было интересно, может ли кто-нибудь указать простой способ. Что касается поиска реализации, в которой используется SSO: как я уже сказал, меня не беспокоит управление памятью. Я уже реализовал это (для этого static_vector), и я мог бы даже использовать static_basic_string на этом, вместо того, чтобы реализовывать это снова. - person sbi; 14.10.2014
comment
Ваш совет отбросить те части интерфейса, которые труднее выполнить, а именно семейство find(), мне кажется достаточно хорошим. Я просто спросил здесь сегодня всех разработчиков, когда они последний раз использовали одну из функций поиска строк, и оказалось, что некоторые никогда их не использовали, другие - раз в десять лет. Так что я пока просто пропущу их. - person sbi; 14.10.2014
comment
@sbi Я примерно такой же, и много разбираюсь. В 99% случаев я просто использую функции из <algorithm>. Точно так же такие вещи, как insert или replace, тоже кажутся довольно редкими; Обычно я создаю копию и просто добавляю. - person James Kanze; 14.10.2014
comment
@ Джеймс: Спасибо. Вставка и замена могут занять некоторое время, чтобы сгладить единичные ошибки :), но в остальном я не вижу в них никаких проблем. Однако найти последнее вхождение подстроки не так просто для алгоритма, если вы хотите сделать это эффективно. Однако подавляющее большинство здесь сказали, что они не использовали это в течение многих лет и, если им это действительно нужно, они всегда могут скопировать строку в стандартную библиотеку и сделать это там. Думаю, тогда я пойду с твоим советом. - person sbi; 14.10.2014
comment
Это можно сделать с помощью настраиваемого глупого распределителя, который просто возвращает буфер, как я показал ниже. Он работает с GCC и LLVM разновидностями STL, как ожидалось (или неожиданно, в зависимости от вашего взгляда). Однако это слишком много проблем. Невозможно получить от basic_string, который работал бы с предварительно выделенным буфером. Вы должны создать буфер, распределитель с этим буфером, а затем строку с этим распределителем. Гораздо проще реализовать небольшой настраиваемый класс, который реплицирует желаемое подмножество интерфейса basic_string, как предлагается в этом ответе. - person ArunasR; 14.10.2014
comment
@arunasr Будет ли работать во всех случаях. Я сам подумал об этом, но потом подумал, что это вызовет проблемы, когда реализация захочет увеличить емкость: она выполнит второе распределение перед освобождением первого и ожидает, что будет указатель на другую память. (Например, insert посередине, где std::string сам подумал, что ему нужно перераспределить. Я могу представить, что он копирует начало, затем новые символы, а затем конец. Если новый указатель указывает на тот же буфер, копирование новых символов перезапишет часть конца.) - person James Kanze; 14.10.2014
comment
@JamesKanze Есть 2 случая, когда строка должна увеличиваться: добавление и вставка. Оба дали положительный результат на GCC и LLVM STL, как это ни странно. - person ArunasR; 15.10.2014
comment
@arunasr Я не думаю, что append будет проблемой: ни один из существующих символов не перемещается, поэтому копия будет сама по себе. insert может работать, в зависимости от порядка копирования и размера вставки относительно количества символов позади нее --- если реализация сначала копирует все старые символы, а количество символов после вставки не больше, чем число вставляемых, это будет работать. И, конечно, если реализация использует что-то вроде memmove и копирует новые символы последними, она также будет работать. - person James Kanze; 15.10.2014
comment
@JamesKanze Мы до смерти обсуждали (как я это сделал с sbi), что может сработать, а что нет ... Я показал, что работает. Приведенный мной код работает с двумя разными реализациями STL. Это не означает, что он будет работать, например, с MSVS по всем причинам, которые вы и sbi указали (и я согласен с ними). Достаточно легко проверить это и доказать так или иначе, если у вас есть компилятор MS. - person ArunasR; 21.10.2014
comment
@arunasr Мы, должно быть, делаем что-то другое, потому что, когда я это пробую, он не работает с g ++, но работает с VC ++. (Но я не проверял это исчерпывающе ни с одним, а с одним простым тестом.) - person James Kanze; 21.10.2014
comment
@arunasr Просто обновление: он работает с VC ++, пока строки не длиннее SSO. Со строками из 100 или более символов (и буфера из 1000) происходит сбой как с g ++, так и с VC ++. - person James Kanze; 21.10.2014
comment
@JamesKanze Возможно, версия g ++ ... моя - 4.8.1. Я также обнаружил, что g ++ неправильно использует распределитель для освобождения; он предоставляет неправильный размер блока для освобождения. Он также странным образом использует конструктор распределителя по умолчанию, повторную привязку и сравнение. Как показало мое расследование, я очень отталкиваюсь от использования g ++ std::basic_string ... CLANG / LLVM работает намного лучше. - person ArunasR; 21.10.2014
comment
@arunasr Или, может быть, мы тестировали разные вещи. В моем случае я создал довольно длинную строку в main, а затем вставил в нее строку, которую я вернул из функции. - person James Kanze; 21.10.2014
comment
@JamesKanze Я сделал именно то, что ниже, не больше и не меньше. С g++ и clang++ -stdlib=libc++ - person ArunasR; 21.10.2014

Это просто, напишите распределитель стека, вот пример:

https://codereview.stackexchange.com/questions/31528/a-working-stack-allocator < / а>

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

person user1095108    schedule 14.10.2014
comment
Это решение выделяет строки внутри пользовательской структуры данных стека, а не в реальном стеке, который компилятор использует для запоминания адреса возврата. Это все еще может работать как решение. Я голосую за. - person Audrius Meskauskas; 14.10.2014
comment
Этот код находится на C ++ 14. Требовалось C ++ 03 ... тем не менее, принцип в силе, его можно переписать. Обратите внимание, что std :: basic_string использует распределитель необычным и замечательным образом, поэтому вам придется добавлять такие вещи, как операторы сравнения. Также обратите внимание, что std :: basic_string (и другие контейнеры) широко используют конструктор копирования распределителя, а stack_store не обрабатывает это изящно. - person ArunasR; 14.10.2014
comment
@arunasr Это код на C ++ 11. По крайней мере, это то, с чем я его компилирую. - person user1095108; 14.10.2014
comment
@ user1095108 Дело в том, что это не C ++ 03 / C ++ 98. И он не будет компилироваться, если используется с basic_string, потому что он не реализует operator== и struct rebind. Я не уверен, как он будет обрабатывать операцию копирования, но, судя по всему, это не будет красиво. - person ArunasR; 14.10.2014

LLVM ADT имеет класс SmallString. В нем также есть SmallVector и много других полезных классов.

В то время как текущая кодовая база LLVM движется к использованию C ++ 11, (не очень) старые версии LLVM поддерживают C ++ 03.

person Abyx    schedule 14.10.2014
comment
Интересный. Можно ли использовать его как замену std::basic_string? - person sbi; 14.10.2014
comment
@sbi его интерфейс похож на basic_string, но у него нет некоторые конструкторы - person Abyx; 14.10.2014
comment
Ммм. Я бы предпочел что-нибудь, что я могу добавить, перекомпилировать и сделать. (Как я уже писал, интерфейс должен быть std::basic_string.) Все равно спасибо! - person sbi; 14.10.2014

Отличной отправной точкой является основанный на политике строковый класс Александреску, описанный в этом Статья доктора Доббса. Он включает политику единого входа, которая в основном делает то, что вы хотите (ищите на странице SmallStringOpt), и ее легко изменить, если / по вашему усмотрению. Он предшествует C ++ 11, так что у вас тоже все в порядке.

person Tony Delroy    schedule 14.10.2014
comment
Я рассмотрел для этого особенности управления памятью. Это переписывание некоторых других операций заставило меня усомниться в моем подходе. - person sbi; 14.10.2014
comment
@sbi переписывание некоторых других операций - когда вы создаете экземпляр шаблона Александреску с политикой единого входа, вы получаете все остальные операции в стиле std::string. Как это не решает вашу озабоченность? - person Tony Delroy; 14.10.2014
comment
Ах, хорошо, если это прямая замена для std::basic_string, это должно сработать. Спасибо, посмотрю. - person sbi; 14.10.2014
comment
@sbi Ага - первый абзац - ... статья предоставляет и объясняет basic_string реализацию на основе политик, которая дает вам двенадцать стандартных реализаций, совместимых со стандартами, с различными оптимизациями и компромиссами .... Ура. - person Tony Delroy; 14.10.2014
comment
Спасибо, я действительно мог позаимствовать эти реализации оттуда (+1). Однако, как указал Джеймс, я мог бы пока оставить их нереализованными, потому что они редко когда-либо нужны. - person sbi; 14.10.2014
comment
@sbi: да, я видел этот разговор - довольно сюрреалистично. Я буду осведомлен об ожиданиях будущих разработчиков, но уверен, что вы сможете взвесить все это и без меня .... Ура. - person Tony Delroy; 14.10.2014
comment
Его нельзя использовать везде, это особая часть кода, обслуживающая определенный API. Строки для взаимодействия с этим API не должны перераспределяться. Если мы можем добавить что-то, что позволяет выполнять все строковые операции, обычно выполняемые с этими строками, все в порядке. Поскольку немногие могут вспомнить, что когда-либо использовали его на всех своих профессиональных носителях, и поскольку никто здесь никогда не использовал его в последние несколько лет, и поскольку этот API не предназначен для общего использования, я думаю, что у нас здесь все хорошо. И если это станет проблемой, мы все равно сможем ее реализовать. - person sbi; 14.10.2014
comment
@TonyD Ожидания разработчиков будущего - это проблема, и это в некоторой степени зависит от организации. Там, где я работаю, не было бы проблем: если бы разработчик использовал функцию, которой не было, он пожаловался бы мне, и я бы ее добавил. По мере необходимости; в среде, где я работаю, это не будет проблемой. Но я работал в других местах, где это было бы. Это зависит от рабочей среды. (Как правило, это будет проблемой при большом количестве программистов. Если вас не слишком много и коммуникация хорошая, то этого не будет.) - person James Kanze; 14.10.2014

Существует множество basic_string реализаций, некоторые полностью основаны на динамическом распределении, некоторые - на динамическом распределении только для строк шире заданной длины (фактически, они используют свой собственный внутренний буфер, когда он подходит).

Использование распределителя, вероятно, не лучший вариант, поскольку интерфейс между строкой и распределителем предполагает, что объект распределителя является частью контейнера, но выделенная память поступает извне самого контейнера. Вы можете организовать это, реализовав распределитель с использованием POSIX alloca, со всеми недостатками.

Проблема при реализации строк в стеке заключается в том, что вы не можете позволить им динамически расти (возможно, в то время в стеке есть что-то большее), но вам также нужно позаботиться о таких операциях, как +=, которые могут сделать строку длиннее и длиннее.

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

Вероятно, существует компромисс, связанный с процессом связи между памятью и кешем (обычно выполняется со 128 байтами или 4 КБ), но он сильно зависит от оборудования, поэтому сложность, которую можно позволить, скорее всего, не окупится.

Более доступным решением может быть распределитель, который по-прежнему выделяет в куче, но имеет возможность сохранять и повторно использовать возвращенные блоки (до определенного предела), уменьшая необходимость запрашивать у системы выделение / освобождение.

Но производительность в этом случае не обязательно улучшится, если базовая система уже реализует new/delete таким образом.

person Emilio Garavaglia    schedule 14.10.2014
comment
Да, это определенно требует превышения доступности. Однако рассматриваемые строки обычно ограничены 16 или 32 символами, так что это не должно быть проблемой. Кроме того, все это должно взаимодействовать с C API, который предполагает заранее выделенные буферы, которые не должны перераспределяться, и, следовательно, также превышают доступность. - person sbi; 14.10.2014
comment
В этом случае вам понадобится size_t плюс пробел, который может содержать 32 символа или указатель (дискриминатор size()<32). Из-за специфики данных вы, вероятно, сделаете его независимым автономным классом, который в конечном итоге будет неявно преобразован в рекламу из std::string. Использование настраиваемых распределителей вместе с basic_string может быть опасным: что произойдет, если basic_string (который предполагает, что данные находятся вне его и могут быть перемещены) попытается переместиться? - person Emilio Garavaglia; 14.10.2014
comment
Вы описываете SSO. Но мы не можем использовать это, потому что это зависит от динамической памяти, которую мы не можем здесь использовать. - person sbi; 14.10.2014
comment
Ага, похоже. Что ж, вот чем я сейчас занимаюсь. - person sbi; 14.10.2014

Думаю, я бы использовал комбинацию VLA, определяемых реализацией, и стандартных алгоритмов.

person Lightness Races in Orbit    schedule 14.10.2014

Это рабочий код, но НЕ РЕКОМЕНДУЕМЫЙ СПОСОБ.

В этом коде много следов, чтобы показать, что он делает. Он не проверяет, не превышает ли размер запроса на выделение размер буфера. При необходимости вы можете это проверить. Обратите внимание, что std :: basic_string пытается выделить больше, чем необходимо.

#include <string>
#include <iostream>

template<typename T, size_t S>
class fixed_allocator
{
  typedef std::allocator<T> _base;

  std::ostream& trace() const { return std::cerr << "TRACE fixed_allocator " << (void*)this ; }

public:
  typedef typename _base::value_type value_type;
  typedef typename _base::pointer pointer;
  typedef typename _base::const_pointer const_pointer;
  typedef typename _base::reference reference;
  typedef typename _base::const_reference const_reference;
  typedef typename _base::size_type size_type;
  typedef typename _base::difference_type difference_type;

  template<class C> struct rebind {
    typedef fixed_allocator<C, S*sizeof(C)/sizeof(T)> other;
  };
  T* buffer_;

  fixed_allocator(T* b) : buffer_(b) { trace() << "ctor: p="  << (void*)b << std::endl; }

  fixed_allocator() : buffer_(0) { trace() << "ctor: NULL" << std::endl; };
  fixed_allocator(const fixed_allocator &that) : buffer_(that.buffer_) { trace() << "ctor: copy " << (void*)buffer_ << " from " << (void*) &that << std::endl; };

  pointer allocate(size_type n, std::allocator<void>::const_pointer hint=0) {
    trace() << "allocating on stack " << n << " bytes" << std::endl;
    return buffer_;
  }

  bool operator==(const fixed_allocator& that) const { return that.buffer_ == buffer_; }
  void deallocate(pointer p, size_type n) {/*do nothing*/}
  size_type max_size() const throw() { return S; }
};

int main()
{
  char buffer_[256];
  fixed_allocator<char, 256> ator(buffer_);
  std::basic_string<char, std::char_traits<char>, fixed_allocator<char, 256> > str(ator);
  str.assign("ipsum lorem");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  std::cout << " has 'l' at " << str.find("l") << std::endl;
  str.append(" dolor sit amet");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  str.insert(0, "I say, ");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  str.insert(7, "again and again and again, ");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  str.append(": again and again and again, ");
  std::cout << "String: '" << str << "' length " << str.size() << std::endl;
  return 0;
}

Этот код был протестирован на GCC и LLVM и работает должным образом (или неожиданно).

Синтаксис громоздкий. Невозможно получить от basic_string и встроить буфер. Намного лучший способ - создать небольшой специализированный класс buffer_string с необходимым подмножеством интерфейса basic_string.

person ArunasR    schedule 14.10.2014
comment
Пробовали ли вы манипулировать строкой после ее первого присвоения, чтобы она увеличивалась? - person sbi; 14.10.2014
comment
Строка создается пустой, а затем обрабатывается (присваивается). Считается ли это манипуляцией, и она должна расти? Я не пробовал ничего большего, чем вы видите в коде, но я ожидаю, что basic_string просто вызовет allocate, возможно, с подсказкой, и все, что он получит, это тот же буфер. - person ArunasR; 14.10.2014
comment
str.append(" dolor sit amet"); работал отлично, если выделить достаточно большой буфер. basic_string попытался выделить 51 байт, что вызвало исключение стека с исходной программой. Увеличив буфер до 60, проблем нет. - person ArunasR; 14.10.2014
comment
Я хочу сказать, что когда строку нужно перераспределить, вы просто возвращаете указатель на текущие данные. Однако строка скопирует старые данные во вновь выделенную память. Да, я забыл, что это будет работать нормально, если не изменить начало строки. Но это все равно неправильно. (Если я - опять же - что-то не упускаю, вставка обнаружит этот недостаток.) - person sbi; 14.10.2014
comment
Не ленитесь! Вырезайте, вставляйте, компилируйте и играйте. str.insert(0, "I say, "); работает отлично. - person ArunasR; 14.10.2014
comment
Вздох. Тогда это из-за превышения доступности строки (capacity()>size()). Просто посмотрите, что std::basic_string на самом деле делает, когда размер нужно увеличить, а затем посмотрите, как это согласуется с вашим распределителем. Это не может работать, даже если может показаться. Кажется, что работает - это просто один из способов самовыражения UB. - person sbi; 14.10.2014
comment
В моих тестах append, insert(0, ...) и insert(7, ...) работают как с GCC, так и с CLANG LLVM. Сообщения трассировки показывают, что размер буфера изменяется (увеличивается); GCC увеличивается в 4 раза (25, 36, 51, 77, 129 байтов), а CLANG увеличивается один раз (48, 129 байтов). Никаких перерасходов. basic_string, кажется, правильно перемещает конец строки перед копированием вставленной строки в промежуток. Я правда не понимаю, чего еще ты хочешь ... (вздыхает) - person ArunasR; 14.10.2014
comment
Конечно, я могу ошибаться. Насколько я понимаю: если строка содержит "lorem ipsum", и вы вставляете "blah " в индекс 5, то, если результирующий размер превышает емкость, строка 1) выделяет новый буфер, а затем либо 2a) скопировать старый буфер в новый и выполнить вставку туда, или 2b) скопировать первую часть старого буфера, добавить новую строку, а затем добавить остаток старого буфера. Наконец, он 3) отбросит старый буфер. 2a будет работать с вашим распределителем. 2b (более эффективный) выйдет из строя. Платформы, которые вы проверили, похоже, используют 2a. - person sbi; 14.10.2014
comment
Я не ожидал, что платформы сделают это. Но, опять же, я могу ошибаться. Возможно, по какой-то причине, о которой я не подумал, поставщики должны использовать менее эффективный copy-first-insert-later. Или мой логический модуль сломан. В любом случае, исходя из моих текущих знаний, я бы не стал делать то, что делаете вы. - person sbi; 14.10.2014
comment
Позвольте нам продолжить это обсуждение в чате. - person ArunasR; 14.10.2014