mirror of https://github.com/orefkov/simstr.git
740 lines
54 KiB
Markdown
740 lines
54 KiB
Markdown
|
|
# Строки в С++
|
|||
|
|
(, что с вами не так?)
|
|||
|
|
|
|||
|
|
<cite>
|
|||
|
|
В ретроспективе 1991 года по истории C++ его создатель Бьярне Страуструп назвал отсутствие стандартного строкового типа
|
|||
|
|
(и некоторых других стандартных типов) в C++ 1.0 худшей ошибкой, которую он допустил при его разработке:
|
|||
|
|
"the absence of those led to everybody re-inventing the wheel and to an unnecessary diversity in the most fundamental classes"
|
|||
|
|
(«Их отсутствие привело к тому, что все заново изобретали велосипед, и к ненужному разнообразию в самых фундаментальных классах»).
|
|||
|
|
</cite>
|
|||
|
|
|
|||
|
|
## Что было и есть
|
|||
|
|
Во вступительной части я хочу немного описать, каково ныне состояние со строками в С++, как мы к нему докатились и почему оно таково.
|
|||
|
|
А также описать недостатки текущих реализаций, чтобы были понятны решения, которые я использую в своей библиотеке строк.
|
|||
|
|
|
|||
|
|
Собственно, изначально как такового стандартного типа для строк в С++ не было.
|
|||
|
|
Для работы со строками использовался подход из C – строка есть указатель на массив байтов, оканчивающихся нулём.
|
|||
|
|
Недостатки таких строк — невозможно в строке использовать байт `0`, т. е. не подходит для бинарных данных,
|
|||
|
|
непонятна стратегия управления/владения ресурсами, ну и основной недостаток — длину строки приходится вычислять каждый раз,
|
|||
|
|
перебирая все её символы.
|
|||
|
|
|
|||
|
|
Откуда ноги растут у такого решения вполне понятно — со времён динозавров: как динозавры были большие, с маленьким мозгом и
|
|||
|
|
короткими ручками, так и компьютеры были большие, память у них была маленькая, а строки короткими. Сэкономить память на хранении длины строки было важнее, чем потерять время на повторный подсчет длины.
|
|||
|
|
|
|||
|
|
Первые попытки стандартизировать строки как класс начались только в С++98 - std::string появился, как часть STL, и как
|
|||
|
|
многое из STL, крайне неоднозначно воспринимался программистами.
|
|||
|
|
|
|||
|
|
И первое, что приходит в голову при улучшении C-строк — надо хранить длину строки:
|
|||
|
|
```cpp
|
|||
|
|
struct simple_string {
|
|||
|
|
const char* data;
|
|||
|
|
size_t length;
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
При наличии такой строки, уже множество алгоритмов значительно оптимизируются.
|
|||
|
|
Например, при сравнении двух строк на равенство мы можем даже не начинать сравнивать их символы, если длины строк не равны.
|
|||
|
|
Более того, этих данных абсолютно достаточно для всех методов, которые не модифицируют строку.
|
|||
|
|
Также заметим, что такой объект на современных 64-битных архитектурах прекрасно передается в функции по значению —
|
|||
|
|
оба его поля укладываются в регистры (ну, кроме windows), что облегчает работу оптимизатору компилятора.
|
|||
|
|
|
|||
|
|
Между тем, такое решение попало в стандарт только аж в С++17, в виде `std::string_view`.
|
|||
|
|
Видимо, только тогда до комитета смогли донести мысль, что строки строкам рознь, и использовать только один универсальный объект
|
|||
|
|
для строк — по меньшей мере может приводить к уменьшению производительности, а также нарушает принцип «не плати за то,
|
|||
|
|
чем не пользуешься». Почему же «строки строкам рознь» и почему нам мало одного типа для строки, рассмотрим как раз далее.
|
|||
|
|
|
|||
|
|
И следующий вопрос, возникающий со строками — это владение ресурсами.
|
|||
|
|
Практически каждый крупный фреймворк решал эту задачу самостоятельно, изобретая свои велосипеды.
|
|||
|
|
У нас есть `std::string`, в QT у нас `QString`, в MFC - `CString`, в ATL - `CAtlString`, свои строки есть в Folly,
|
|||
|
|
в общем, “тысячи их”, любой игровой движок начинают с того, чтобы написать свои строки.
|
|||
|
|
|
|||
|
|
Многие из этих реализаций в аспекте управления ресурсами для улучшения производительности использовали подход
|
|||
|
|
**COW** – “Copy On Write”. При этом объект строки ссылался на некий разделяемый между несколькими объектами буфер с символами
|
|||
|
|
строки и счётчиком ссылок на этот буфер, что позволяло быстро создавать копию строки.
|
|||
|
|
|
|||
|
|
Но все они совпадали в одном — строка всегда предполагалась мутабельной, то есть что мы можем модифицировать символы в буфере строки.
|
|||
|
|
|
|||
|
|
Из-за этого подход **COW** умер к С++11: при каждой операции, могущей модифицировать символы строки приходилось проверять,
|
|||
|
|
не ссылаемся ли мы на разделяемый буфер и если да, то копировать символы в другой буфер.
|
|||
|
|
В многопоточной же среде потом ещё и проверять, не надо ли теперь освобождать старый буфер, и естественно всё это обмазавшись
|
|||
|
|
локами или атомиками, что тоже не бесплатно.
|
|||
|
|
Поэтому, начиная с С++11 `std::string` не использует **COW**, и каждое копирование объекта строки приводит и к копированию
|
|||
|
|
всех символов строки в другой буфер.
|
|||
|
|
|
|||
|
|
Естественно, что каждый новый буфер требует аллокации памяти, что пытаются немного оптимизировать за счёт **SSO** –
|
|||
|
|
“Small String Optimization”, когда объект строки содержит внутри себя небольшой буфер и символы коротких строк
|
|||
|
|
располагаются прямо в нём.
|
|||
|
|
Но это уже зависит от реализации: в одних библиотеках помещают в объект строки до 15 байт, в некоторых до 23.
|
|||
|
|
Однако эта оптимизация тоже палка о двух концах, и может в различных реализациях сделать строку "non trivial moveable".
|
|||
|
|
|
|||
|
|
А без COW мутабельность строк приводит к тому, что любая инициализация объекта строки приводит к копированию байтов.
|
|||
|
|
Посмотрим такой код:
|
|||
|
|
```cpp
|
|||
|
|
const char* text1 = "Hello, World"; // ничего не стоит
|
|||
|
|
std::string_view text2 = "Hello, World"; // Ничего не стоит, вычисляет длину строки при компиляции
|
|||
|
|
std::string text3 = "Hello, World"; // В рантайме каждый раз копирует символы строки
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
(Удостоверится в правдивости комментариев можно на https://godbolt.org/z/51oKGWT5T )
|
|||
|
|
|
|||
|
|
Но если нам дальше по коду не нужно никак модифицировать строку, мы зря платим за аллокацию, копирование символов,
|
|||
|
|
а также за деструктор строки. То есть хотелось бы иметь как минимум два варианта строк — мутабельные и иммутабельные,
|
|||
|
|
чтобы явно дать понять компилятору, что мы не собираемся модифицировать строку.
|
|||
|
|
Или банальный пример — мы парсим какой-то входящий буфер данных, нам нужно проверить, равен ли некий кусок буфера строке
|
|||
|
|
”hello” на «чистом С++», т. е. без всяких memcmp и strcmp. До появления string_view приходилось делать примерно так:
|
|||
|
|
```cpp
|
|||
|
|
bool is_part_buffer_equal_hello(const char* data, int start, int end) {
|
|||
|
|
return std::string(data + start, end - start) == "hello";
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
Тут получается, сначала копируются символы из буфера data в буфер временной строки, возможно с аллокацией памяти, и лишь
|
|||
|
|
потом временная строка сравнивается с ”hello”, а потом ещё и деструктор и раскрутка стека на случай исключения.
|
|||
|
|
|
|||
|
|
При использовании же вместо `std::string` `std::string_view` – код на C++ почти не меняется:
|
|||
|
|
```cpp
|
|||
|
|
bool is_part_buffer_equal_hello_view(const char* data, int start, int end) {
|
|||
|
|
return std::string_view(data + start, end - start) == "hello";
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
Однако генерируемый машинный код значительно преобразуется, достигая уровня ручного С-кода — там просто сравнивается,
|
|||
|
|
что end – start == 5 и дальше кусок начального буфера сравнивается через memcmp со строкой ”hello”
|
|||
|
|
(при -O2 c константами 1819043176 (’hell’) и 111 (’o’)).
|
|||
|
|
Ни создания временного объекта, ни копирования байтов, ни деструктора, ни раскрутки стека для исключений.
|
|||
|
|
Убедится можно на https://godbolt.org/z/9fo188e7c
|
|||
|
|
|
|||
|
|
Казалось бы, ну вот же в С++17 появился `string_view`, пожалуйста, используй его в параметрах своих функций вместо `const std::string&`,
|
|||
|
|
и будет счастье. Но тут тоже есть нюанс — всё отлично работает, пока нам не нужно передать строку в стороннее C-API: string_view не даёт
|
|||
|
|
гарантий нуль-терминированности строки, поэтому его data() нельзя передать в стороннее C-API, и потому всё-равно придётся сначала
|
|||
|
|
скопировать его в `std::string`. А раз нужен `std::string`, то и параметром функции оптимальнее cделать `const std::string&`
|
|||
|
|
и далее по цепочке, все параметры вновь станут `const std::string&`.
|
|||
|
|
|
|||
|
|
Далее, после инициализации строки, самая частая мутабельная операция с ними, скорее всего конкатенация строк, либо в виде просто
|
|||
|
|
сложения строк, либо добавления строки к строке. И именно она легко может вызывать как неоптимальную производительность при неграмотном
|
|||
|
|
использовании, так и оверхед по памяти, даже при грамотном использовании.
|
|||
|
|
|
|||
|
|
Рассмотрим простой код ( https://godbolt.org/z/odx7W1Pv7 )
|
|||
|
|
```cpp
|
|||
|
|
#include <string>
|
|||
|
|
void some_outer_function(const std::string&);
|
|||
|
|
|
|||
|
|
void func(const std::string& s1, const std::string& s2) {
|
|||
|
|
std::string concat = s1 + s2 + "hello";
|
|||
|
|
some_outer_function(concat);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
Как видим, и в clang, и в GCC создается несколько временных объектов, в которые последовательно перекладываются символы строк,
|
|||
|
|
и как результат — мы получаем несколько лишних аллокаций для промежуточных буферов, символы из строк копируются несколько лишних
|
|||
|
|
раз из промежуточных буферов. В идеале для лучшей производительности такой код нужно переписать так:
|
|||
|
|
```cpp
|
|||
|
|
#include <string>
|
|||
|
|
void some_outer_function(const std::string&);
|
|||
|
|
|
|||
|
|
void func(const std::string& s1, const std::string& s2) {
|
|||
|
|
static const std::string_view hello = "hello";
|
|||
|
|
std::string concat;
|
|||
|
|
concat.reserve(s1.size() + s2.size() + hello.size());
|
|||
|
|
concat += s1;
|
|||
|
|
concat += s2;
|
|||
|
|
concat += hello;
|
|||
|
|
some_outer_function(concat);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
К сожалению, пока ни один компилятор не оптимизирует первый простой код до уровня второго более оптимального кода, а писать
|
|||
|
|
такой код каждый раз руками довольно неудобно. То есть опять приходится платить за то, чем не пользуешься.
|
|||
|
|
Да и в этом случае вполне может возникнуть оверхед по памяти — операции добавления строки обычно во всех реализациях увеличивают
|
|||
|
|
размер буфера строки не меньше, чем в два раза, считая, что скоро к строке могут снова что-нибудь добавить.
|
|||
|
|
Поэтому если строку не планируется более модифицировать, но время её жизни ещё не подошло к концу (например, это поле
|
|||
|
|
какого-либо класса), нужно ещё не забыть сделать на ней `shrink_to_fit`.
|
|||
|
|
|
|||
|
|
Между тем, часто основной сценарий использования строк — это как раз некая подготовка строки путём нескольких модификаций и конкатенаций,
|
|||
|
|
а затем она где-то хранится, более не меняясь. При этом программист обычно знает, примерно какой размер строк ожидается в этом месте,
|
|||
|
|
и мог бы выделить буфер для этих промежуточных модификаций прямо на стеке, прибегая к динамической аллокации только при превышении
|
|||
|
|
размера этого буфера. Однако с текущей реализацией строк это сделать довольно проблематично, либо неудобно.
|
|||
|
|
|
|||
|
|
Подытожим, что имеем на данный момент:
|
|||
|
|
|
|||
|
|
- «Из коробки» в С++ для работы со строками сейчас имеется `std::string`.
|
|||
|
|
- Строки подразумеваются мутабельными, что приводит к обязательному копированию всех символов строки при инициализации и
|
|||
|
|
копировании объектов строк.
|
|||
|
|
- Соответственно, не имеем возможности быстрого копирования строк, даже если не планируем потом менять копию.
|
|||
|
|
- Конкатенация нескольких строк — задача, могущая выполнятся неоптимально, приводить к оверхеду по памяти, написать оптимальный код сложно.
|
|||
|
|
- Есть костыль для иммутабельных строк в виде `std::string_view`, однако он не решает вопросы владения строкой, поэтому по сути
|
|||
|
|
годится только как тип для передачи параметров в функции, не меняющие строки, с оговоркой, что не может использоваться в функциях,
|
|||
|
|
вызывающих C-API, так как не даёт гарантий нуль-терминированности.
|
|||
|
|
- Ну и к `std::string` есть вопросы, что несмотря на то, что это класс для строк, собственно для работы со строками в нём крайне куцый
|
|||
|
|
функционал по сравнению с тем, к чему привыкли в других языках — к примеру нет замены подстрок по шаблону (в других языках это обычно
|
|||
|
|
replace, но в С++ эта функция делает совершенно другое), trim, split, join, upper, lower и т. п.
|
|||
|
|
Эти функции приходится каждый раз писать самому, и не факт, что у всех это получится оптимально.
|
|||
|
|
|
|||
|
|
Надеюсь, после этого небольшого вступления вам будет более понятно, какие проблемы я решал своей строковой библиотекой и каким образом.
|
|||
|
|
|
|||
|
|
## Библиотека simstr
|
|||
|
|
Собственно, нельзя сказать, что «я свелосипедил свою реализацию класса для строк».
|
|||
|
|
Как я ранее показал, сложно, а то и даже невозможно написать один единый строковый класс, хорошо подходящий для всех сценариев
|
|||
|
|
использования. Именно поэтому у меня не строковый класс, а строковая библиотека, которая содержит несколько разных строковых типов,
|
|||
|
|
от более простых к более сложным, каждый из которых имеет свои сильные и слабые стороны, и пользователю нужно грамотно подходить к
|
|||
|
|
вопросу, какой из этих классов в каком случае стоит использовать.
|
|||
|
|
|
|||
|
|
Саму библиотеку я начал потихоньку разрабатывать ещё в 2011-2012 годах, когда у нас уже появилась семантика перемещения, но ещё
|
|||
|
|
не было std::string_view. Однако сейчас минимальная версия стандарта для работы библиотеки: **C++20** – используются концепты и \<format\>.
|
|||
|
|
|
|||
|
|
Сначала я расскажу о классах библиотеки для самих строк, а потом о том, как в ней оптимально решается задача конкатенации строк.
|
|||
|
|
|
|||
|
|
Несколько общих моментов:
|
|||
|
|
- Все классы для работы со строками шаблонизированы типом символов, но подразумевается, что символы могут быть char, char16_t,
|
|||
|
|
char32_t, wchar_t.
|
|||
|
|
- Все строки имеют явную длину.
|
|||
|
|
- Классы владельцы строк хранят их с завершающим нулем в конце, который не входит в длину строки.
|
|||
|
|
- В самой строке могут содержаться нулевые символы, все алгоритмы работают только через длину строки, не обращая на них внимания.
|
|||
|
|
- Классы владельцы строк могут инициализироваться строками другого типа символов, выполняя конвертацию между UTF-8, UTF-16, UTF-32.
|
|||
|
|
- Для смены регистра символов и сравнения строк без учёта регистра используются встроенные таблицы для первой плоскости юникода
|
|||
|
|
(до 0xFFFF). Строки считаются представленными в кодировке UTF-8, UTF-16, UTF-32 соответственно.
|
|||
|
|
Однако не делается нормализация строк и не обрабатываются ситуации, когда смена регистра символа приводит к изменению их количества.
|
|||
|
|
Если вам нужна строгая работа с юникодом, используйте другие средства, например ICU.
|
|||
|
|
|
|||
|
|
### Классы строк.
|
|||
|
|
|
|||
|
|
#### Первый самый простой класс строки называется, естественно, `simple_str` :)
|
|||
|
|
(simstr::simple_str)
|
|||
|
|
|
|||
|
|
Класс просто представляет собой указатель на начало константной строки и её длину, по сути то же самое, что `std::string_view`.
|
|||
|
|
Предназначен для работы с иммутабельными строками, не владеющий ими, то есть вы должны сами озаботиться тем, что реальная строка,
|
|||
|
|
представленная через `simple_str` – жива во время его использования.
|
|||
|
|
|
|||
|
|
Реализует все строковые методы, не модифицирующие строку.
|
|||
|
|
|
|||
|
|
Алиасы:
|
|||
|
|
- `ssa` для simple_str\<char\>
|
|||
|
|
- `ssu` для simple_str\<char16_t\>
|
|||
|
|
- `ssw` для simple_str\<wchar_t>
|
|||
|
|
- `ssuu` для simple_str\<char32_t\>
|
|||
|
|
|
|||
|
|
Применяется в основном для передачи строк как параметр функций, не модифицирующих переданную строку, вместо `const std::string&`,
|
|||
|
|
а также для локальных переменных при работе с частями строк.
|
|||
|
|
|
|||
|
|
#### Второй класс — `simple_str_nt`
|
|||
|
|
(simstr::simple_str_nt)
|
|||
|
|
|
|||
|
|
По устройству и назначению совпадает с `simple_str`, но дает гарантии нуль-терминированности строки.
|
|||
|
|
То есть если функции надо переданный параметр без изменений передать дальше как C-строку в какое то API, она должна использовать для
|
|||
|
|
параметра тип `simple_str_nt`.
|
|||
|
|
Все классы владеющих строк (simstr::sstring, simstr::lstring) могут быть преобразованы в `simple_str_nt`, так как хранят строки с завершающим нулём.
|
|||
|
|
Это позволяет писать функции с единым типом параметра, принимающим на вход любой тип владеющих строковых объектов.
|
|||
|
|
|
|||
|
|
Алиасы:
|
|||
|
|
- `stra` для simple_str_nt\<char>
|
|||
|
|
- `stru` для simple_str_nt\<char16_t>
|
|||
|
|
- `strw` для simple_str_nt\<wchar_t>
|
|||
|
|
- `struu` для simple_str_nt\<char32_t>
|
|||
|
|
|
|||
|
|
Может инициализироваться строковыми литералами:
|
|||
|
|
```cpp
|
|||
|
|
stra text = "Text";
|
|||
|
|
```
|
|||
|
|
Длина в этом случае вычисляется сразу при компиляции. Аналогично `simple_str_nt` создается с помощью `operator""_ss`:
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
stringa result = "Count: "_ss + count;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Класс sstring (shared string).
|
|||
|
|
(simstr::sstring)
|
|||
|
|
|
|||
|
|
Класс, могущий хранить иммутабельную строку.
|
|||
|
|
То есть ему можно присвоить некую строку только целиком, модифицировать символы строки нельзя.
|
|||
|
|
|
|||
|
|
Владеет строкой, управляет памятью для символов строки.
|
|||
|
|
Хранит со строками завершающий нуль, и может быть источником для `simple_str_nt`, для передачи в C-API.
|
|||
|
|
Так же, как и `simple_str`, реализует все методы, не модифицирующие строку.
|
|||
|
|
|
|||
|
|
Алиасы:
|
|||
|
|
- `stringa` для sstring\<char>
|
|||
|
|
- `stringu` для sstring\<char16_t>
|
|||
|
|
- `stringw` для sstring\<wchar_t>
|
|||
|
|
- `stringuu` для sstring\<char32_t>
|
|||
|
|
|
|||
|
|
|
|||
|
|
То, что хранимая строка иммутабельна, позволяет применить ряд оптимизаций:
|
|||
|
|
- Для строк, не подходящих для SSO, использует общий разделяемый буфер с атомарным счётчиком ссылок.
|
|||
|
|
Позволяет быстро копировать строку без необходимости блокировок доступа к содержимому буфера.
|
|||
|
|
- Нет необходимости хранить размер буфера (capacity) — всё равно мы ничего не дописываем в буфер.
|
|||
|
|
- Позволяет просто ссылаться на литералы программы, не копируя их символы в какой-либо буфер:
|
|||
|
|
```cpp
|
|||
|
|
stringa str = "Hello!"; // Ничего не стоит, не копирует байты строки
|
|||
|
|
stringa ltr = stra{"Hello!"}; // А вот тут копирует байты строки в ltr
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Также в классе применяется **SSO** – Small String Optimization.
|
|||
|
|
Короткие строки помещаются внутри самого объекта во внутренний буфер.
|
|||
|
|
|
|||
|
|
Размеры:
|
|||
|
|
|
|||
|
|
Для 64 бит:
|
|||
|
|
- `stringa` – класс 24 байта, SSO до 23 символов.
|
|||
|
|
- `stringu` – класс 32 байта, SSO до 15 символов.
|
|||
|
|
- `stringuu` – класс 32 байта, SSO до 7 символов.
|
|||
|
|
|
|||
|
|
Для 32 бит:
|
|||
|
|
- `stringa` – класс 16 байт, SSO до 15 символов.
|
|||
|
|
- `stringu` – класс 24 байта, SSO до 11 символов.
|
|||
|
|
- `stringuu` – класс 24 байта, SSO до 5 символов.
|
|||
|
|
|
|||
|
|
#### Класс lstring<K, N, forShared> (local string)
|
|||
|
|
(simstr::lstring)
|
|||
|
|
|
|||
|
|
Класс, хранящий строку и позволяющий её модифицировать.
|
|||
|
|
Владеет строкой, управляет памятью для символов строки.
|
|||
|
|
Хранит со строками завершающий нуль, и может быть источником для `simple_str_nt`, для передачи в C-API.
|
|||
|
|
Как и все остальные классы, реализует все методы, не модифицирующие строку.
|
|||
|
|
|
|||
|
|
В качестве `N` в параметре шаблона задаётся размер внутреннего буфера для хранения символов.
|
|||
|
|
Строки длиной до N символов хранятся внутри объекта, а при превышении этого количества — аллоцируется динамический буфер,
|
|||
|
|
в который сохраняются символы. При копировании объекта все символы также всегда копируются.
|
|||
|
|
|
|||
|
|
Если `forShare` == true и символы не помещаются в локальный буфер, то динамический буфер создается с дополнительным местом,
|
|||
|
|
так чтобы совпадать по структуре с буфером `sstring`. Тогда при перемещении `lstring` в `sstring` – переместится только указатель
|
|||
|
|
на буфер, без излишнего копирования символов.
|
|||
|
|
|
|||
|
|
Этот класс удобен для работы со строками как локальная переменная на стеке.
|
|||
|
|
Обычно мы предполагаем примерный размер строк, с котороми будем работать, и можем создать локальную строку с буфером на стеке,
|
|||
|
|
и работать с ней. При этом не опасаясь переполнения буфера, так как в этом случае строка переключится на динамический буфер.
|
|||
|
|
|
|||
|
|
Алиасы:
|
|||
|
|
- `lstringa<N=16>` для lsrting\<char, N, false>
|
|||
|
|
- `lstringu<N=16>` для lsrting\<char16_t, N, false>
|
|||
|
|
- `lstringw<N=16>` для lsrting\<wchar_t, N, false>
|
|||
|
|
- `lstringuu<N=16>` для lsrting\<char32_t, N, false>
|
|||
|
|
- `lstringsa<N=16>` для lsrting\<char, N, true>
|
|||
|
|
- `lstringsu<N=16>` для lsrting\<char16_t, N, true>
|
|||
|
|
- `lstringsw<N=16>` для lsrting\<wchar_t, N, true>
|
|||
|
|
- `lstringsuu<N=16>` для lsrting\<char32_t, N, true>
|
|||
|
|
|
|||
|
|
|
|||
|
|
Небольшой пример использования с пояснениями:
|
|||
|
|
```cpp
|
|||
|
|
#ifdef _WIN32
|
|||
|
|
const char path_separator = '\\';
|
|||
|
|
#else
|
|||
|
|
const size_t MAX_PATH = 260;
|
|||
|
|
const char path_separator = '/';
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
auto get_current_dir() {
|
|||
|
|
#ifdef _WIN32
|
|||
|
|
/* заполняем буфер wchar_t строки lstringw<MAX_PATH> из GetCurrentDirectoryW с возможным
|
|||
|
|
увеличением буфера и конвертируем в ut8 char. В конструкторе используется то, что появилось
|
|||
|
|
только в С++23 как `resize_and_overwrite`, а у нас было изначально :) */
|
|||
|
|
|
|||
|
|
lstringa<MAX_PATH> path{lstringw<MAX_PATH>{ [](auto p, auto s) { return GetCurrentDirectoryW(DWORD(s + 1), p); }}};
|
|||
|
|
|
|||
|
|
/* Эта одна строчка делает примерно то же самое, что и вот такой код.
|
|||
|
|
typedef struct lstringa_MAX_PATH_t {
|
|||
|
|
char* data;
|
|||
|
|
size_t length;
|
|||
|
|
size_t capacity;
|
|||
|
|
char local_buffer[MAX_PATH + 1];
|
|||
|
|
} lstringa_MAX_PATH;
|
|||
|
|
|
|||
|
|
lstringa_MAX_PATH* get_current_dir(lstringa_MAX_PATH* result) {
|
|||
|
|
wchar_t buffer[MAX_PATH + 1], *buf = buffer;
|
|||
|
|
DWORD size = sizeof(buffer) / sizeof(wchar_t), lengthOfpath;
|
|||
|
|
for (;;) {
|
|||
|
|
// Возвращает либо количество скопированных символов без учёта завершающего нуля,
|
|||
|
|
// либо если буфер мал, то нужный размер буфера вместе с завершающим нулём
|
|||
|
|
DWORD ret = GetCurrentDirectoryW(size, buf);
|
|||
|
|
if (ret < size) {
|
|||
|
|
// Влезло в буфер, хотя в Windows пути могут быть и длиннее, чем MAX_PATH, если начинаются с \\?\
|
|||
|
|
// https://learn.microsoft.com/ru-ru/windows/win32/fileio/maximum-file-path-limitation?tabs=registry
|
|||
|
|
lenOfpath = ret;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
size = ret;
|
|||
|
|
if (buf != buffer)
|
|||
|
|
free(buf);
|
|||
|
|
buf = malloc(size);
|
|||
|
|
}
|
|||
|
|
utf16toUtf8(buf, lengthOfPath, result);
|
|||
|
|
if (buf != buffer)
|
|||
|
|
free(buf);
|
|||
|
|
return result;
|
|||
|
|
}
|
|||
|
|
*/
|
|||
|
|
#else
|
|||
|
|
lstringa<MAX_PATH> path{ [](char* p, size_t s) {
|
|||
|
|
const char* res = getcwd(p, s + 1);
|
|||
|
|
if (res) {
|
|||
|
|
return stra{res}.length(); // Возвращаем длину строки
|
|||
|
|
}
|
|||
|
|
if (errno == ERANGE) // Не влезло в буфер, попробуем в два раза больше
|
|||
|
|
return s * 2;
|
|||
|
|
return 0ul;
|
|||
|
|
}};
|
|||
|
|
#endif
|
|||
|
|
// Удостоверимся, что строка будет заканчиваться разделителем директорий
|
|||
|
|
if (!path.length() || path.at(-1) != path_separator) {
|
|||
|
|
path += e_c(1, path_separator);
|
|||
|
|
}
|
|||
|
|
return path;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
stringa build_full_path(ssa fileName) {
|
|||
|
|
return get_current_dir() + fileName + ".txt";
|
|||
|
|
/*
|
|||
|
|
Здесь сначала на стеке создастся временный объект lstringa<MAX_PATH> для вызова get_current_dir.
|
|||
|
|
Функция get_current_dir заполнит его названием текущего каталога.
|
|||
|
|
В 99.9% случаев для этого хватит локального буфера на стеке.
|
|||
|
|
После рассчитывается общая длина для результата: длина current_dir + длина fileName + 4.
|
|||
|
|
Определяется буфер для строки конечного результата - если длина меньше 24 — строка будет размещена прямо в stringa,
|
|||
|
|
иначе аллоцируется буфер для результирующей строки сразу нужного размера.
|
|||
|
|
Затем в буфер результирующей строки последовательно копируются символы из current_dir, file_name, ".txt";
|
|||
|
|
Ну и благодаря RVO - место для самого результата (stringa) - отводится в вызывающей функции,
|
|||
|
|
то есть никакого дополнительного копирования при возврате не будет.
|
|||
|
|
|
|||
|
|
Таким образом, будет максимум всего две аллокации памяти (если current_dir не влезет в MAX_PATH),
|
|||
|
|
или одна, если результирующая строка длиннее 23 символов, при этом эта аллокация будет сразу нужного размера.
|
|||
|
|
*/
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
В этом примере вы наверняка заметили, как конкатенируются строки и задались вопросом — как же при двух сложениях длина всего результата считалась всего один раз, чтобы выделить необходимое место сразу, без промежуточных буферов?
|
|||
|
|
|
|||
|
|
Ответ на этот вопрос:
|
|||
|
|
|
|||
|
|
### Строковые выражения
|
|||
|
|
Дело в том, что в библиотеке нет сложения строковых объектов как такового. Сложение выполняется для «строковых выражений».
|
|||
|
|
|
|||
|
|
*Строковое выражение* — это любой объект произвольного типа, имеющий функции `length` и `place`.
|
|||
|
|
Функция `length` – возвращает длину строки, функция `place` – помещает символы строки в переданный ей буфер.
|
|||
|
|
|
|||
|
|
Любая владеющая строка (simstr::sstring, simstr::lstring) может инициализироваться строковым выражением — она запрашивает у него длину,
|
|||
|
|
выделяет место для хранения символов, и передает это место строковому выражению, вызывая его функцию place.
|
|||
|
|
|
|||
|
|
Для строковых выражений определена шаблонная функция сложения:
|
|||
|
|
```cpp
|
|||
|
|
template<StrExpr A, StrExprForType<typename A::symb_type> B>
|
|||
|
|
inline auto operator + (const A& a, const B& b) {
|
|||
|
|
return strexprjoin<A, B>{a, b};
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`strexprjoin` – шаблонный тип, который сам является строковым выражением.
|
|||
|
|
В себе он хранит ссылки на два переданных ему строковых выражения.
|
|||
|
|
При запросе длины он выдает сумму длин двух строковых выражений, а при размещении символов — сначала размещает
|
|||
|
|
в переданном буфере первое выражение, затем второе.
|
|||
|
|
```cpp
|
|||
|
|
template<StrExpr A, StrExprForType<typename A::symb_type> B>
|
|||
|
|
struct strexprjoin {
|
|||
|
|
using symb_type = typename A::symb_type;
|
|||
|
|
const A& a;
|
|||
|
|
const B& b;
|
|||
|
|
constexpr strexprjoin(const A& a_, const B& b_) : a(a_), b(b_){}
|
|||
|
|
constexpr size_t length() const noexcept { return a.length() + b.length(); }
|
|||
|
|
constexpr symb_type* place(symb_type* p) const noexcept { return b.place(a.place(p)); }
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Таким образом, операция сложения нескольких строковых выражений создает объект, также являющийся строковым выражением,
|
|||
|
|
который рекурсивно хранит ссылки на слагаемые части, каждая из которых знает свой размер и умеет размещать себя
|
|||
|
|
в буфере результата. А чтобы получить готовый результат сложения, это выражение надо "материализовать", то есть
|
|||
|
|
присвоить любому владеющему стровому объекту, который выделит всю память под результат за один раз и запустит
|
|||
|
|
размещение символов в этой памяти.
|
|||
|
|
|
|||
|
|
Все строковые типы библиотеки сами являются строковыми выражениями, то есть могут служить слагаемыми в конкатенациях
|
|||
|
|
строковых выражений.
|
|||
|
|
|
|||
|
|
Также `operator+` определён для строковых выражений и строковых литералов, строковых выражений и чисел (числа конвертируются
|
|||
|
|
в десятичное представление), а также вы можете сами добавить желаемые типы.
|
|||
|
|
|
|||
|
|
Пример:
|
|||
|
|
```cpp
|
|||
|
|
stringa text = header + " count=" + count + ", done";
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Существует несколько типов строковых выражений "из коробки", для выполнения различных операций со строками:
|
|||
|
|
|
|||
|
|
#### expr_spaces<ТипСимвола, КоличествоСимволов, Символ = ' '>{}
|
|||
|
|
Выдает строку длиной КоличествоСимволов, заполненную заданным символом. Количество символов и символ - константы времени
|
|||
|
|
компиляции. Для некоторых случаев есть сокращенная запись:
|
|||
|
|
|
|||
|
|
e_spca(КоличествоСимволов) - строка char пробелов
|
|||
|
|
e_spcw(КоличествоСимволов) - строка w_char пробелов
|
|||
|
|
|
|||
|
|
#### expr_pad<ТипСимвола>{КоличествоСимволов, Символ = ' '}
|
|||
|
|
Выдает строку длиной КоличествоСимволов, заполненную заданным символом.
|
|||
|
|
Количество символов и символ могут задаваться в рантайме. Сокращенная запись:
|
|||
|
|
|
|||
|
|
e_c(КоличествоСимволов, Символ)
|
|||
|
|
|
|||
|
|
#### e_choice(bool Condition, StrExpr1, StrExpr2)
|
|||
|
|
Если Condition == true, результат будет равен StrExpr1, иначе StrExpr2.
|
|||
|
|
|
|||
|
|
#### e_if(bool Condition, StrExpr1)
|
|||
|
|
Если Condition == true, результат будет равен StrExpr1, иначе пустая строка.
|
|||
|
|
|
|||
|
|
#### expr_num<ТипСимвола>(ЦелоеЧисло)
|
|||
|
|
Конвертирует число в десятичное представление. Редко используется, так как для строковых выражений и чисел
|
|||
|
|
переопределен оператор "+", и число можно просто написать как `text + number`;
|
|||
|
|
|
|||
|
|
#### expr_real<ТипСимвола>(ВещественноеЧисло)
|
|||
|
|
конвертирует число в десятичное представление. Редко используется, так как для строковых выражений и чисел
|
|||
|
|
переопределен оператор "+", и число можно просто написать как `text + number`;
|
|||
|
|
|
|||
|
|
#### e_join<bool ПослеПоследнего = false, bool ТолькоНеПустые = false>(контейнер, "Разделитель")
|
|||
|
|
Конкатенирует все строки в контейнере, используя разделитель. Если ПослеПоследнего == true,
|
|||
|
|
то разделитель добавляется и после последнего элемента контейнера, иначе только между элементами.
|
|||
|
|
Если ТолькоНеПустые == true, то пустые строки пропускаются без добавления разделителя.
|
|||
|
|
|
|||
|
|
#### e_repl(ИсходнаяСтрока, "Искать", "Заменять")
|
|||
|
|
Заменяет в исходной строке вхождения "Искать" на "Заменять".
|
|||
|
|
Шаблоны поиска и замены - строковые литералы времени компиляции.
|
|||
|
|
|
|||
|
|
#### expr_replaced<ТипСимвола>{ИсходнаяСтрока, Искать, Заменять}
|
|||
|
|
Заменяет в исходной строке вхождения Искать на Заменять.
|
|||
|
|
Шаблоны поиска и замены - могут быть любыми строковыми объектами в рантайме.
|
|||
|
|
|
|||
|
|
#### empty_expr<ТипСимвола>
|
|||
|
|
Выдает пустую строку. Сокращённая запись — eea, eeu, eew, eeuu. Применяется если формирование строки начинается с числа и строкового литерала:
|
|||
|
|
```cpp
|
|||
|
|
str = eea + count + " times.";
|
|||
|
|
```
|
|||
|
|
так как оператор сложения определён только для сложения строкового выражения и числа.
|
|||
|
|
Также замечу, что существует `operator""_ss`, который превращает строковый литерал в объект `simple_str_nt`, который уже является строковым выражением:
|
|||
|
|
```cpp
|
|||
|
|
str = "Count = "_ss + count;
|
|||
|
|
...
|
|||
|
|
str = count + " times."_ss;
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### Свои строковые выражения
|
|||
|
|
Вы можете сами создавать свои типы строковых выражений для оптимального формирования строк в нужных вам целях и алгоритмах.
|
|||
|
|
Для этого просто создайте тип с методами `length`, `place` и `typename symb_type`.
|
|||
|
|
Примеры создания и использования из реальных проектов:
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
/* Сформировать строку в JSON формате, в 16 битных символах */
|
|||
|
|
struct expr_json_str {
|
|||
|
|
using symb_type = u16s;
|
|||
|
|
ssu text;
|
|||
|
|
size_t l;
|
|||
|
|
size_t length() const noexcept {
|
|||
|
|
return l;
|
|||
|
|
}
|
|||
|
|
u16s* place(u16s* ptr) const noexcept;
|
|||
|
|
expr_json_str(ssu t);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline expr_json_str::expr_json_str(ssu t) : text(t) {
|
|||
|
|
const u16s* ptr = text.symbols();
|
|||
|
|
size_t add = 0;
|
|||
|
|
|
|||
|
|
for (size_t i = 0; i < text.length(); i++) {
|
|||
|
|
switch (*ptr++) {
|
|||
|
|
case '\b':
|
|||
|
|
case '\f':
|
|||
|
|
case '\r':
|
|||
|
|
case '\n':
|
|||
|
|
case '\t':
|
|||
|
|
case '\"':
|
|||
|
|
case '\\':
|
|||
|
|
add++;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
l = text.len + add;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
inline u16s* expr_json_str::place(u16s* ptr) const noexcept {
|
|||
|
|
const u16s *r = text.symbols();
|
|||
|
|
size_t lenOfText = text.length(), lenOfTail = l;
|
|||
|
|
while (lenOfTail > lenOfText) {
|
|||
|
|
u16s s = *r++;
|
|||
|
|
switch (s) {
|
|||
|
|
case '\b':
|
|||
|
|
*ptr++ = '\\';
|
|||
|
|
*ptr++ = 'b';
|
|||
|
|
lenOfTail--;
|
|||
|
|
break;
|
|||
|
|
case '\f':
|
|||
|
|
*ptr++ = '\\';
|
|||
|
|
*ptr++ = 'f';
|
|||
|
|
lenOfTail--;
|
|||
|
|
break;
|
|||
|
|
case '\r':
|
|||
|
|
*ptr++ = '\\';
|
|||
|
|
*ptr++ = 'r';
|
|||
|
|
lenOfTail--;
|
|||
|
|
break;
|
|||
|
|
case '\n':
|
|||
|
|
*ptr++ = '\\';
|
|||
|
|
*ptr++ = 'n';
|
|||
|
|
lenOfTail--;
|
|||
|
|
break;
|
|||
|
|
case '\t':
|
|||
|
|
*ptr++ = '\\';
|
|||
|
|
*ptr++ = 't';
|
|||
|
|
lenOfTail--;
|
|||
|
|
break;
|
|||
|
|
case '\"':
|
|||
|
|
*ptr++ = '\\';
|
|||
|
|
*ptr++ = '\"';
|
|||
|
|
lenOfTail--;
|
|||
|
|
break;
|
|||
|
|
case '\\':
|
|||
|
|
*ptr++ = '\\';
|
|||
|
|
*ptr++ = '\\';
|
|||
|
|
lenOfTail--;
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
*ptr++ = s;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
lenOfTail--;
|
|||
|
|
lenOfText--;
|
|||
|
|
}
|
|||
|
|
if (lenOfTail) {
|
|||
|
|
std::char_traits<u16s>::copy(ptr, r, lenOfTail);
|
|||
|
|
ptr += lenOfTail;
|
|||
|
|
}
|
|||
|
|
return ptr;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Использование:
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
........
|
|||
|
|
chunked_string_builder<u16s> vtText;
|
|||
|
|
........
|
|||
|
|
vtText << uR"({"#type":"jxs:string","#value":")" + expr_json_str(name) + u"\"}";
|
|||
|
|
.......
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Ещё пример
|
|||
|
|
```cpp
|
|||
|
|
/* Нужно сформировать бинарные данные в BASE64 формате, в 16 битных символах */
|
|||
|
|
struct expr_str_base64 {
|
|||
|
|
using symb_type = u16s;
|
|||
|
|
ssa text;
|
|||
|
|
size_t length() const noexcept {
|
|||
|
|
return (text.len + 2) / 3 * 4;
|
|||
|
|
}
|
|||
|
|
u16s* place(u16s* ptr) const noexcept;
|
|||
|
|
expr_str_base64(ssa t) : text(t) {}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline u16s* expr_str_base64::place(u16s* ptr) const noexcept {
|
|||
|
|
static constexpr u8s alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|||
|
|
|
|||
|
|
const unsigned char* t = (const unsigned char*)text.str;
|
|||
|
|
|
|||
|
|
size_t i = 0;
|
|||
|
|
if (text.len > 2) {
|
|||
|
|
for (; i < text.len - 2; i += 3) {
|
|||
|
|
*ptr++ = alphabet[(t[i] >> 2) & 0x3F];
|
|||
|
|
*ptr++ = alphabet[((t[i] & 0x3) << 4) | ((int)(t[i + 1] & 0xF0) >> 4)];
|
|||
|
|
*ptr++ = alphabet[((t[i + 1] & 0xF) << 2) | ((int)(t[i + 2] & 0xC0) >> 6)];
|
|||
|
|
*ptr++ = alphabet[t[i + 2] & 0x3F];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (i < text.len) {
|
|||
|
|
*ptr++ = alphabet[(t[i] >> 2) & 0x3F];
|
|||
|
|
if (i == (text.len - 1)) {
|
|||
|
|
*ptr++ = alphabet[((t[i] & 0x3) << 4)];
|
|||
|
|
*ptr++ = '=';
|
|||
|
|
} else {
|
|||
|
|
*ptr++ = alphabet[((t[i] & 0x3) << 4) | ((int)(t[i + 1] & 0xF0) >> 4)];
|
|||
|
|
*ptr++ = alphabet[((t[i + 1] & 0xF) << 2)];
|
|||
|
|
}
|
|||
|
|
*ptr++ = '=';
|
|||
|
|
}
|
|||
|
|
return ptr;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Использование:
|
|||
|
|
```cpp
|
|||
|
|
......
|
|||
|
|
chunked_string_builder<u16s> vtText;
|
|||
|
|
......
|
|||
|
|
vtText << u"{\"#\",87126200-3e98-44e0-b931-ccb1d7edc497,{1,{#base64:" + expr_str_base64(v) + u"}}},";
|
|||
|
|
......
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
И ещё
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
/* Нужно преобразовать tm в строку даты/времени в 16-битных символах */
|
|||
|
|
struct expr_str_tm {
|
|||
|
|
using symb_type = u16s;
|
|||
|
|
const tm& t;
|
|||
|
|
size_t length() const noexcept {
|
|||
|
|
return 19;
|
|||
|
|
}
|
|||
|
|
u16s* place(u16s* ptr) const noexcept;
|
|||
|
|
expr_str_tm(const tm& _t) : t(_t) {}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
inline u16s* expr_str_tm::place(u16s* ptr) const noexcept {
|
|||
|
|
if constexpr (sizeof(wchar_t) == 2) {
|
|||
|
|
// Под Windows можно сразу форматнуть строку в нужный буфер
|
|||
|
|
std::swprintf((wchar_t*)ptr, 20, L"%04i-%02i-%02i %02i:%02i:%02i", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,
|
|||
|
|
t.tm_hour, t.tm_min, t.tm_sec);
|
|||
|
|
} else {
|
|||
|
|
// Сначала форматнём в промежуточный буфер, потом скопируем в результат
|
|||
|
|
char buf[20];
|
|||
|
|
std::snprintf(buf, 20, "%04i-%02i-%02i %02i:%02i:%02i", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, t.tm_hour,
|
|||
|
|
t.tm_min, t.tm_sec);
|
|||
|
|
for (unsigned i = 0; i < 19; i++) {
|
|||
|
|
ptr[i] = buf[i];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
return ptr + 19;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Использование
|
|||
|
|
|
|||
|
|
```cpp
|
|||
|
|
......
|
|||
|
|
bool makeBind(SqliteQuery& query, tVariant& param, unsigned paramNum) {
|
|||
|
|
switch (param.vt) {
|
|||
|
|
......
|
|||
|
|
case VTYPE_DATE:
|
|||
|
|
query.bind(paramNum, lstringu<30>{expr_str_tm{winDateToTm(param.date)}}.to_str());
|
|||
|
|
break;
|
|||
|
|
case VTYPE_TM:
|
|||
|
|
query.bind(paramNum, lstringu<30>{expr_str_tm{param.tmVal}}.to_str());
|
|||
|
|
break;
|
|||
|
|
......
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
ВНИМАНИЕ: обычно поля в объектах строковых выражений являются ссылками на исходные данные.
|
|||
|
|
И ссылки эти почти всегда ведут на локальные или временные объекты. Поэтому крайне рискованно возвращать строковые выражения
|
|||
|
|
из функций — надо сто раз проверить, что в них не попали ссылки на локальные или временные переменные.
|
|||
|
|
Возьмите за правило — можно легко передавать строковые выражения в функции, и опасно возвращать их из функций.
|
|||
|
|
Лучше при возврате материализовать строковое выражение в строковый объект, содержащий итоговую строку.
|
|||
|
|
При желании тип возвращаемой строки можно задать шаблонным параметром.
|
|||
|
|
|
|||
|
|
|
|||
|
|
### Класс chunked_string_builder
|
|||
|
|
|
|||
|
|
Предназначен для конкатенации множества строк.
|
|||
|
|
Когда вам нужно последовательно формировать длинный текст из множества небольших кусочков (например, формируете html ответ
|
|||
|
|
и т. п.) - последовательно складывать всё в один строковый объект крайне неоптимально — будет много переаллокаций и
|
|||
|
|
перекопирования уже накопленных символов. В этом случае удобно использовать chunked_string_builder — всё, что он умеет,
|
|||
|
|
это прибавлять строку к накопленным символам. Однако делает он это не в единый последовательный буфер памяти, а в отдельные
|
|||
|
|
буфера, не меньше чем заданное выравнивание. При заполнении очередного буфера он просто создает ещё один буфер и продолжает
|
|||
|
|
складывать данные в него.
|
|||
|
|
|
|||
|
|
То есть допустим вы задали выравнивание 1024.
|
|||
|
|
Добавили несколько строк, заполнили буфер на 100 символов. И добавляете строку длинной 3000 символов.
|
|||
|
|
При этом 924 символа скопируются в первый буфер, заполнив его до конца.
|
|||
|
|
Для оставшихся 2076 создастся буфер размером 3072 символа, и они скопируются в него, в нём останется место для 996 символов.
|
|||
|
|
Так последовательно каждый буфер заполняется до конца, и имеет размер кратный заданному выравниванию.
|
|||
|
|
Таким образом избегаются переаллокации и перекопирование обработанных символов.
|
|||
|
|
|
|||
|
|
После окончательного заполнения вы можете работать с накопленными данными — либо слить все буфера в одну последовательную
|
|||
|
|
строку (размер для буфера которой вы теперь уже знаете), либо перебирать их по отдельности, например, посылая эти буфера
|
|||
|
|
в сеть. Либо последовательно копируя данные в буфер заданного размера.
|