simstr 1.6.1
Yet another strings library
 
Загрузка...
Поиск...
Не найдено
Библиотека simstr.

simstr - библиотека строковых объектов и функций

On English | По-английски

CMake on multiple platforms

Версия 1.6.1.

Ускорь работу со строками в 2-10 раз!

В этой библиотеке содержится современная реализация нескольких видов строковых объектов и различных алгоритмов для работы со строками.

Сгенерированная документация

Находится здесь

Краткое описание

Цель библиотеки - сделать работу со строками в С++ такой же простой и лёгкой, как во множестве других языков, особенно скриптовых, но при этом сохранив оптимальность и производительность на уровне С и C++, и даже улучшив их.

Не секрет, что работа со строками в С++ зачастую доставляет боль. Класс std::string часто неудобен либо неэффективен. Многих функций, обычно необходимых при работе со строками, просто нет, и их каждому приходится писать самому. Даже элементарно конкатенировать std::string и std::string_view стало возможно только с C++26. Именно поэтому я начал примерно в 2012 году создавать для себя эту библиотеку, и теперь готов поделится ею со всеми C++ разработчиками.

Эта библиотека не делалась как универсальный комбайн, который "может всё", я реализовывал то, что мне приходилось использовать в работе, стараясь сделать это наиболее эффективным способом, и скромно надеюсь, что кое-что у меня получилось и пригодится другим людям, либо напрямую, либо как источник идей.

Библиотека содержит две части:

  • Реализация "Строковых выражений" и алгоритмов работы с константными строками.\ Для использования этой части достаточно просто взять файл "include/simstr/strexpr.h" и написать в своём коде
    #include "путь/к файлу/strexpr.h"
    Это позволит вам для стандартных строковых типов (std::basic_string, std::basic_string_view) использовать мощные и быстрые "строковые выражения" для конкатенации и построения строк, а также упрощенные варианты классов simple_str и simple_str_nt, которые реализуют все те строковые алгоритмы библиотеки, которые не требуют хранения или модификации строк. Так как это header-only часть, она не включает в себя работу с UTF-кодировками и упрощённый Unicode.
  • Полная версия, требующая подключения всей библиотеки ("include/simstr/sstring.h"), добавляет свои строковые типы с возможностями хранения и модификации строк, работает с UTF-кодировками и упрощённым Unicode.

Библиотека не претендует на роль "поменял хедер и всё заработало лучше" - она прекрасно уживается вместе со стандартными строками и не меняет поведение уже существующего кода, работающего с ними. Многие методы в ней я старался делать совместимыми с std::string и std::string_view, но особо с этим не заморачивался. Переписывание вашего кода на работу с simstr потребует некоторых усилий, но уверяю, что они окупятся. А благодаря совместимости со стандартными строками эту работу можно делать поэтапно, небольшими кусками. Новый же код работы со строками создавать с её применением легко и доставляет удовольствие :)

Основное отличие simstr от std::string - для работы со строками используется не единый универсальный класс, а несколько видов объектов, каждый из которых хорош для своих целей, и при этом хорошо взаимодействующих друг с другом. Если вы активно использовали std::string_view и понимали, в чём его преимущества и недостатки по сравнению с std::string, то подход simstr вам также будет понятен.

Основные возможности библиотеки

При использовании только #include "simstr/strexpr.h":

  • Поддержка работы со строками char, char8_t, char16_t, char32_t, wchar_t.
  • Мощная и расширяемая система "Строковых выражений". Позволяет эффективно реализовать преобразование и сложение (конкатенацию) строк, строковых литералов, чисел (и возможно других объектов), добиваясь значительного ускорения строковых операций. Совместима как со строковыми объектами simstr, так и со стандартными строками (std::basic_string, std::basic_string_view), что позволяет применять быструю конкатенацию и там, где пока не получается отказаться от стандартных строк. Тоже позволяет смешивать в операциях строки совместимых типов символов.
  • Константные строковые функции (не меняют исходную строку):
    • Получение подстрок.
    • Сравнение строк, сравнение строк без учёта регистра ASCII-символов.
    • Поиск подстрок и символов - с начала или с конца строки.
    • Различный тримминг строк - справа, слева, везде, по пробельным символам, по заданным символам.
    • Замена подстрок (созданием копии строки с заменой).
    • Замена набора символов на набор соответствующих подстрок (созданием копии строки с заменой).
    • Слияние (join) контейнеров строк в единую строку, с заданием разделителей и опций - "пропускать пустые", "разделитель после последней".
    • Разбиение (split) строк на части по заданному разделителю. Разбиение возможно сразу в контейнер со строками, либо вызовом функтора для каждой подстроки, либо путем итерации с помощью итератора Splitter.
  • Функции модификации стандартных строк строковыми выражениями:
    • str::append, str::prepend, str::insert, str::change - меняют str::string строковыми выражениями, например str::append(text, "count = "_ss + count).
    • str::replace - заменяет вхождения искомой подстроки на строку замены или строковое выражение. Если подстрока не найдена, строковое выражение даже не вычисляется.
  • Парсинг целых чисел с возможностью "тонкой" настройки при компиляции - можно задавать опции проверки переполнения, пропуск пробельных символов, конкретное основание счисления либо автовыбор по префиксам 0x, 0, 0b, 0o, допустимость знака +. Парсинг реализован для всех видов строк и символов.
  • Парсинг double для char и wchar_t, а также совместимых с ними по размеру типов символов.

При использовании полной версии библиотеки:

  • Всё то же, что и перечислено выше, плюс
  • Дополнительные эффективные строковые объекты - sstring (shared string), lstring (local string).
  • lstring - поддерживает множество мутабельных операций со строками - различные замены, вставки, удаления и т.п. Позволяет задавать размер для внутреннего буфера символов, что может превращать Small String Optimization в Big String Optimization :).
  • Прозрачное преобразование строк из одного типа символов в другой, с автоматической конвертацией между UTF-8, UTF-16, UTF-32, используя simdutf. Строки "совместимых" типов преобразуются простым копированием: char <-> char8_t, wchar_t <-> char32_t в Linux, wchar_t <-> char16_t в Windows.
  • Интеграция с функциями форматирования format и sprintf (с автоматическим увеличением буфера). Форматирование возможно для строк char, wchar_t и строк, совместимых с ними по размеру. То есть под Windows это char8_t, char16_t, под Linux - char8_t, char32_t (писать свою библиотеку форматирования для всех видов символов не входило в мои замыслы).
  • Содержится минимальная поддержка Unicode при преобразовании upper, lower и регистро-независимом сравнении строк. Работает только для символов первой плоскости Unicode (до 0xFFFF), а при смене регистра не учитываются случаи, когда один code point может преобразовываться в несколько, то есть преобразование регистра символов соответствует std::towupper, std::towlower для unicode локали, только быстрее и может работать с любым видом символов.
  • Реализован hash map для ключей строкового типа, на базе std::unordered_map, с возможностью более эффективного хранения и сравнения ключей по сравнению с ключами std::string. Поддерживается возможность регистро-независимого сравнения ключей (Ascii или минимальный Unicode (см. предыдущий пункт)).

Бенчмарки

Бенчмарки производятся с использованием фреймворка Google benchmark. Постарался сделать замеры для наиболее типичных операций, встречающихся в обычной работе. Я проводил замеры на своём оборудовании, под Windows и Linux (в WSL), с использованием компиляторов MSVC, Clang, GCC. Сторонние результаты приветствуются. Также проводил замеры в WASM, сборка в Emscripten. Обращаю внимание, что под WASM в Emscripten собирается 32-битная сборка, а значит, размеры буферов SSO в объектах меньше.

На странице релизов вы можете скачать бинарные сборки бенчмарков и запустить их на своём оборудовании.

Также вы можете запустить Emscripten сборку бенчмарков прямо в браузере. (Перед переходом по ссылке лучше предварительно откройте "Инструменты разработчика" (обычно F12), чтобы видеть консоль Javascript, так как до окончания бенчмарков страница не будет обновляться, а весь вывод будет виден в консоли).

Строковые выражения

Это специальные объекты, которые эффективно реализуют конкатенацию строк, с помощью operator+. Главный принцип, за счёт которого достигается эффективная работа - сколько бы операндов не входило во всё выражение, никаких временных (промежуточных) строк не создаётся, общая длина всего результата подсчитывается только один раз, один раз выделяется память под буфер символов результата, после чего символы копируются сразу в буфер результата на своё место. Никаких перевыделений памяти, никакого передвигания символов в различных промежуточных буферах - всё максимально эффективно. Благодаря возможностям шаблонов C++ и перегрузке операторов, выражение пишется максимально приближённо к обычному синтаксису сложения строк. Кроме того, есть специальные перегрузки для сложения строковых объектов и строковых литералов, строк и чисел, для копирования с заменой, для слияния контейнеров строк и многое другое. Благодаря расширяемости этой системы - возможно создание новых вариантов построения строк, развитие постоянно продолжается.

Все строковые объекты из simstr - сами являются строковыми выражениями, то есть их можно использовать в операциях конкатенации строковых выражений напрямую. Стандартные строки (std::basic_string, std::basic_string_view) - тоже могут служить операндами в операциях сложения со строковыми выражениями. Либо их можно легко преобразовать в строковое выражение, поставив перед ними унарный +.

Примеры использования

Сложение строк с числами

std::string s1 = "start ";
int i;
....
// Было
std::string str = s1 + std::to_string(i) + " end";
// Стало
std::string str = +s1 + i + " end";

+s1 - преобразует std::string в объект - строковое выражение, для которого есть эффективная конкатенация с числами и строковыми литералами.

По бенчмаркам ускорение 1.6 - 2 раза.

Сложение строк с числами в hex-формате

....
// Было
std::string str = s1 + std::format("0x{:x}", i) + " end";
// Стало
std::string str = +s1 + e_hex<HexFlags::Short>(i) + " end";

Ускорение в 9 - 14 раз!!!

Сложение нескольких литералов и поиск в std::string_view

// Было так
size_t find_pos(std::string_view src, std::string_view name) {
// before C++26 we can not concatenate string and string_view...
return src.find("\n- "s + std::string{name} + " -\n");
}
// При использовании только "strexpr.h" стало так
size_t find_pos(ssa src, ssa name) {
return src.find(std::string{"\n- " + name + " -\n"});
}
// А при использовании полной библиотеки можно сделать так
size_t find_pos(ssa src, ssa name) {
// В этом варианте если результат конкатенации вмещается в 207 символов - она производится в буфере на стеке,
// без алокации и освобождения памяти, ускорение в несколько раз. И только если результат длиннее 207 символов -
// будет всего одна аллокация, и конкатенация будет сразу в алоцированный буфер, без перекопирования символов.
return src.find(lstringa<200>{"\n- " + name + " -\n"});
}

ssa - псевдоним для simple_str<char> - аналог std::string_view, позволяет с минимальными расходами принимать параметром функции любой строковый объект, который не нужно модифицировать или передавать в C-API: std::string, std::string_view, "строковый литерал", simple_str_nt, sstring, lstring. Также так как он при этом является ещё и "строковым выражением", то позволяет легко строить конкатенации с его участием.

По замерам ускорение 1.5 - 9 раз.

Сложение с условиями

// Было
std::string buildTypeName(std::string_view type_name, size_t prec, size_t scale) {
std::string res{type_name};
if (prec) {
res += "(" + std::to_string(prec);
if (scale) {
res += "," + std::to_string(scale);
}
res += ")";
}
return res;
}
// Стало при использовании только strexpr.h и желании использовать только стандартные строки
std::string buildTypeName(std::string_view type_name, size_t prec, size_t scale) {
if (prec) {
// + превращает type_name из string_view в строковое выражение
return +type_name + "(" + prec + e_if(scale, ","_ss + scale) + ")";
}
return type_name;
}
// Стало при использовании только strexpr.h и simple_str строки
std::string buildTypeName(ssa type_name, size_t prec, size_t scale) {
if (prec) {
// ssa уже является строковым выражением, + перед ним не нужен
return type_name + "(" + prec + e_if(scale, ","_ss + scale) + ")";
}
return type_name;
}
// Стало при использовании полной библиотеки
stringa buildTypeName(ssa type_name, size_t prec, size_t scale) {
if (prec) {
return type_name + "(" + prec + e_if(scale, ","_ss + scale) + ")";
}
return type_name;
}
constexpr expr_if< A > e_if(bool c, const A &a)
Создание условного строкового выражения expr_if.
Определения strexpr.h:1513

При prec != 0, ускорение 1.5 - 2.2 раза.

Сложение с заменами

// Было
// Стандартной аналога функции replace из других ЯП нет, напишем свою "в лоб".
std::string str_replace(std::string_view from, std::string_view pattern, std::string_view repl) {
std::string result;
for (size_t offset = 0;;) {
size_t pos = from.find(pattern, offset);
if (pos == std::string::npos) {
result += from.substr(offset);
break;
}
result += from.substr(offset, pos - offset);
result += repl;
offset = pos + pattern.length();
}
return result;
}
std::string make_str_str(std::string_view from, std::string_view pattern, std::string_view repl) {
return "<" + str_replace(from, pattern, repl) + ">";
}
// Стало - копирование с заменами
std::string make_str_exp(std::string_view from, std::string_view pattern, std::string_view repl) {
return "<" + e_repl(from, pattern, repl) + ">";
}
constexpr auto e_repl(A &&w, T &&p, X &&r)
Получить строковое выражение, генерирующее строку с заменой всех вхождений заданной подстроки.
Определения strexpr.h:4570

Ускорение от 1.5 раз и выше - в зависимости от содержимого строк.

Разбиение строк на части, парсинг чисел

// Было - разбить строку по разделителю и подсчитать сумму чисел
int split_and_calc_total_str(std::string_view numbers, std::string_view delimiter) {
int total = 0;
for (size_t start = 0; start < numbers.length(); ) {
int delim = numbers.find(delimiter, start);
if (delim == std::string::npos) {
delim = numbers.size();
}
std::string part{numbers.substr(start, delim - start)};
total += std::strtol(part.c_str(), nullptr, 0);
start = delim + delimiter.length();
}
return total;
}
// Стало
int split_and_calc_total_sim(ssa numbers, ssa delimiter) {
int total = 0;
for (auto splitter = numbers.splitter(delimiter); !splitter.is_done();) {
total += splitter.next().as_int<int>();
}
return total;
}

Ускорение в 2-3 раза.

Помимо приведённых здесь отдельных примеров, можно посмотреть исходники:

Основные объекты библиотеки

Доступны при любом использовании:

  • simple_str<K> - самая простая строка (или кусок строки), иммутабельная, не владеющая, аналог std::string_view.
  • simple_str_nt<K> - то же самое, только заявляет, что заканчивается 0. Для работы со сторонними C-API.

Доступны при использовании всей библиотеки:

  • sstring<K> - shared string, иммутабельная, владеющая, с разделяемым буфером символов, поддержка SSO.
  • lstring<K, N> - local string, мутабельная, владеющая, с задаваемым размером SSO буфера.

При подключении только strexpr.h - типы simple_str<K> и simple_str_nt<K> не содержат методов для работы с UTF и Unicode.

Статьи

Использование

Библиотеку можно использовать частично, просто взяв файл "include/simstr/strexpr.h" и включив в свои исходники

#include "include/simstr/strexpr.h"

Это подключит только строковые выражения и упрощённые реализации simple_str и simple_str_nt, без функций работы с UTF и Unicode.

Полная же версия библиотеки simstr состоит из трёх заголовочных файлов и двух исходников. Можно подключать как CMake проект через add_subdirectory (библиотека simstr), можно просто включить файлы в свой проект. Для сборки также требуется simdutf (при использовании CMake скачивается автоматически).

Подключение через FetchContent

function(add_simstr)
set(SIMSTR_BUILD_TESTS OFF)
set(SIMSTR_BENCHMARKS OFF)
FetchContent_Declare(
GIT_REPOSITORY https://github.com/orefkov/simstr.git
GIT_SHALLOW TRUE
GIT_TAG tags/rel1.6.1 # Укажите нужный релиз
FIND_PACKAGE_ARGS NAMES simstr 1.6.1
)
FetchContent_MakeAvailable(simstr)
endfunction()
add_simstr()
target_link_libraries(<your target> PUBLIC simstr::simstr)
Пространство имён для объектов библиотеки
Определения sstring.cpp:12

Библиотека также включена в vcpkg, подключается как orefkov-simstr.

Для работы simstr требуется компилятор стандарта не ниже С++20 - используются концепты и std::format. Работа проверялась под Windows на MSVC-19 и Clang-19, под Linux - на GCC-13 и Clang-21. Также проверялась работа в WASM, сборка в Emscripten 4.0.6, Clang-21.

Удобная отладка

Вместе с библиотекой поставляются два файла, делающие просмотр simstr строковых объектов в отладчиках более удобным.\ Более подробно описано здесь.

Где уже используется

Также simstr используется в моих проектах:

  • simjson - библиотека для простой работы с JSON с использованием строк simstr.
  • simrex - обёртка для работы с регулярными выражениями Oniguruma с использованием строк simstr.
  • v8sqlite - внешняя компонента для 1С-Предприятия V8 для работы с sqlite.