6 возможностей C++23, улучшающих строку и string_view

В этой записи блога давайте соберем несколько изменений, которые будут поставляться с C++23 и все они связаны с strings или string_views.

std::string и std::string_view имеют contains.

Одним из полезных дополнений C++20 к картам была функция-член contains. Мы могли заменить громоздкий для чтения запрос myMap.find(key) != myMap.end() на очень простой для понимания myMap.contains(key). В C++23, std::string и std::string_view будут иметь аналогичные возможности. Вы можете вызвать contains() со строкой или символом, и он вернет true или false в зависимости от того, содержит ли запрашиваемая string или string_view входной параметр.

#include <iostream>
#include <string>
#include <iomanip>

int main() {
    std::string s{"there is a needle in the haystack"};
    std::string_view sv{"acdef"};

    if (s.contains("needle")) {
        std::cout << "we found a needle in: " << std::quoted(s) << 'n';
    }

    if (!sv.contains('b')) {
        std::cout << "we did not find a 'b' in: " << std::quoted(sv) << 'n';
    }
}
/*
we found a needle in: "there is a needle in the haystack"
we did not find a 'b' in: "acdef"
*/
Вход в полноэкранный режим Выход из полноэкранного режима

Больше нет неопределенного поведения из-за конструирования из nullptr

В одном из предыдущих выпусков мы говорили о том, что инициализация string из nullptr является неопределенным поведением. На практике это может произойти, когда вы преобразуете const char * в string. Что произойдет тогда? Это зависит от компилятора, gcc, например, выбрасывает исключение во время выполнения.

Благодаря P2166R1 об этом не стоит беспокоиться.

Вместо неопределенного поведения конструктор и оператор присваивания, перегруженные nullptr_t, удаляются, и поэтому компиляция завершается неудачно, когда вы пытаетесь построить новую string из nullptr.

std::string s(nullptr);
/*
<source>:18:26: error: use of deleted function 'std::__cxx11::basic_string<_CharT, _Traits, _Alloc>::basic_string(std::nullptr_t) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>; std::nullptr_t = std::nullptr_t]'
   18 |     std::string s(nullptr);
      |                          ^
/opt/compiler-explorer/gcc-12.1.0/include/c++/12.1.0/bits/basic_string.h:734:7: note: declared here
  734 |       basic_string(nullptr_t) = delete;
      |       ^~~~~~~~~~~~
*/
Вход в полноэкранный режим Выход из полноэкранного режима

Хотя это изменение хорошо и указывает в хорошем направлении, не все наши проблемы исчезают с nullptrs. Принятие nullptr и размера в конструкторе (например, std::string s(nullptr, 3)) по-прежнему актуально и остается неопределенным поведением.

Эти изменения также действительны для string_view.

Построение std::string_view из диапазонов

С C++23 наш любимый string_view не только теряет конструктор (перегрузка с nullptr удаляется), но и получает новый. Вскоре мы сможем создавать конструктор непосредственно из диапазона.

До сих пор, если мы хотели создать string_view из «диапазона», мы должны были вызвать конструктор с итераторами begin и end: std::string_view sv(myRange.begin(), myRange.end());. Теперь мы сможем напрямую построить string_view на основе диапазона: std::string_view sv(myRange);.

basic_string::resize_and_overwrite()

Одной из основных причин использования языка C++ является его высокая производительность. Область, где мы часто используем язык неэффективно — это работа со строками. C++23 принесет нам еще одну функцию-член string, которая поможет нам работать со строками более производительным способом.

std::string::resize_and_overwrite() принимает два параметра, счетчик и операцию, и делает следующее (при этом ничего не возвращает):

  • если count меньше или равно size() строки, то стирает последние элементы size() - count.
  • если count больше, чем size(), добавляет n - size() инициализированных по умолчанию элементов
  • он также вызывает erase(begin() + op(data(), count), end()).

Другими словами, resize_and_overwrite() убедится, что заданная строка имеет непрерывное хранение, содержащее count + 1 символов.

Если op() бросает, поведение не определено. Оно также не определено, если пытается изменить count.

Но что может быть операцией?

Операция — это функция или объект функции для установки нового содержимого строки, и она принимает два параметра. Первый — это указатель на первый символ в хранилище строки, а второй, такой же, как count, максимально возможный новый размер строки. Она должна вернуть фактическую новую длину строки.

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

При правильном использовании она поможет добавить новый контент или переписать существующий. Или же вы можете удалить содержимое. Чтобы проиллюстрировать последнее, давайте посмотрим на второй пример из оригинальной документации.

std::string s { "Food: " };
s.resize_and_overwrite(10, [](char* buf, int n) {
    return std::find(buf, buf + n, ':') - buf;
});
std::cout << "2. " << std::quoted(s) << 'n';
// 2. "Food"
Вход в полноэкранный режим Выход из полноэкранного режима

Даже если s будет изменен до 10, операция вернет позицию : в строке, что означает, что она будет усечена с этой точки.

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

Требование к span & basic_string_view быть тривиально копируемыми

P2251R1 обновляет требования стандарта к std::span и std::string_view. Начиная с C++23 они должны удовлетворять концепции TriviallyCopyable.

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

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

: string-stream с буфером на основе std::span

C++23 вводит заголовок <spanstream>. Потоки являются старой частью стандартной библиотеки C++. В настоящее время широко используются потоки строк. Строки (и векторы) хранят данные вне себя. Когда объем данных, подлежащих хранению, увеличивается, хранилище и уже сохраненные данные могут автоматически и динамически перераспределяться. Часто это приемлемо, но когда это не так, нам нужен другой вариант.

<spanstream> собирается предоставить такой вариант, они предоставляют фиксированные буферы. Вы должны позаботиться о выделении при создании потока, но вам не нужно беспокоиться о дорогостоящем перераспределении базового буфера, когда он исчерпан. Когда я писал, что вы должны позаботиться о выделении буфера, я действительно имел это в виду. Буфер не принадлежит объекту потока, его жизнью должен управлять программист.

Заключение

Я надеюсь, что вам понравилась эта статья, и вы также получили удовольствие от всех этих разнообразных возможностей, связанных с string/string_view, которые принесет нам C++23. А чего вы ждете больше всего?

Подключайтесь глубже

Если вам понравилась эта статья, пожалуйста

  • нажмите на кнопку «Мне нравится»,
  • подпишитесь на мою рассылку
  • и давайте общаться в Twitter!

Оцените статью
devanswers.ru
Добавить комментарий