Введение в класс std::string_view в С++

  Дмитрий Бушуев  | 

  |

  Обновл. 15 Сен 2021  | 

 41280

 ǀ   8 

На этом уроке мы рассмотрим класс std::string_view, который является нововведением стандарта С++17.

Проблемы строк

На уроке о строках C-style мы говорили об опасностях, которые возникают при их использовании. Конечно, строки C-style работают быстро, но при этом их использование не является таким уж простым и безопасным в сравнении с std::string.

Правда, стоит отметить, что и у std::string имеются свои недостатки, особенно когда речь заходит об использовании константных строк.

Рассмотрим следующий пример:

Результат выполнения программы:

hello hello hello

Внутри функции main() выполняется копирование строки hello 3 раза, в результате чего мы имеем 4 копии исходной строки:

   первая копия — это непосредственно сам строковый литерал hello, который создается на этапе компиляции и хранится в бинарном виде;

   еще одна копия создается при инициализации массива типа char;

   далее идут объекты str и more класса std::string, каждый из которых, в свою очередь, создает еще по одной копии строки.

Из-за того, что класс std::string спроектирован так, чтобы его объекты могли быть изменяемыми, каждому объекту класса std::string приходится хранить свою собственную копию строки. Благодаря этому, исходная строка может быть изменена без влияния на другие объекты std::string.

Это также справедливо и для константных строк (const std::string), несмотря на то, что подобные объекты не могут быть изменены.

Введение в класс std::string_view


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

В стандарте C++17 вводится еще один способ использования строк — с помощью класса std::string_view, который находится в заголовочном файле string_view.

В отличие от объектов класса std::string, которые хранят свою собственную копию строки, класс std::string_view обеспечивает представление (англ. «view») для заданной строки, которая может быть определена где-нибудь в другом месте.

Попробуем переписать код предыдущего примера, заменив каждое вхождение std::string на std::string_view:

В результате мы получим точно такой же вывод на экран, как и в предыдущем примере, но при этом у нас не будут созданы лишние копии строки hello. Когда мы копируем объект класса std::string_view, то новый объект std::string_view будет «смотреть» на ту же самую строку, на которую «смотрел» исходный объект. Ко всему прочему, класс std::string_view не только быстр, но и обладает многими функциями, которые мы изучили при работе с классом std::string:

Т.к. объект класса std::string_view не создает копии строки, то, изменив исходную строку, мы, тем самым, повлияем и на её представление в связанном с ней объектом std::string_view:

Изменяя arr, можно видеть, как изменяется и str. Это происходит из-за того, что исходная строка является общей для этих переменных. Стоит отметить, что при использовании объектов класса std::string_view лучше избегать модифицирования исходной строки, пока существуют связанные с ней объекты класса std::string_view, так как в противном случае, это может привести к путанице и ошибкам.

Совет: Используйте std::string_view вместо строк C-style. Для строк, которые не планируете изменять в дальнейшем, предпочтительнее использовать класс std::string_view вместо std::string.

Функции, модифицирующие представление

Вернемся к нашей аналогии с окном, только теперь рассмотрим окно с занавесками. Мы можем закрыть часть окна левой или правой занавеской, тем самым уменьшив то, что можно увидеть сквозь окно. Заметьте, мы не изменяем объекты, находящиеся снаружи окна, изменяется лишь сектор наблюдения из окна.

Аналогично и с классом std::string_view: в нем содержатся функции, позволяющие нам управлять представлением строки. Благодаря этому мы можем изменять представление строки без изменения исходной строки.

Для этого используются следующие функции:

   remove_prefix() — удаляет символы из левой части представления;

   remove_suffix() — удаляет символы из правой части представления.

Например:

Результат выполнения программы:

Peach
each
ea

В отличие от настоящих занавесок, с помощью которых мы закрыли часть окна, объекты класса std::string_view нельзя «открыть обратно». Изменив однажды область видимости, вы уже не сможете вернуться к первоначальным значениям (стоит отметить, что есть приемы, которые позволяют решить данную проблему, но вдаваться в них мы не будем).

std::string_view и обычные строки


В отличие от строк C-Style, объекты классов std::string и std::string_view не используют нулевой символ (нуль-терминатор) в качестве метки для обозначения конца строки. Данные объекты знают, где заканчивается строка, т.к. отслеживают её длину:

Результат выполнения программы:

aeiou

Проблемы владения и доступа

Поскольку std::string_view является всего лишь представлением строки, его время жизни не зависит от времени жизни строки, которую он представляет. Если отображаемая строка выйдет за пределы области видимости, то std::string_view больше не сможет её отображать и при попытке доступа к ней мы получим неопределенные результаты:

Результат выполнения программы:

What's your name?
nascardriver
Hello nascardriver
Your name is �P@�P@

Когда мы объявили переменную str и с помощью std::cin присвоили ей определенное значение, то данная переменная создала внутри себя строку, разместив её в динамической области памяти. После того, как переменная str вышла за пределы области видимости в конце функции askForName(), внутренняя строка вслед за этим прекратила свое существование. При этом объект класса std::string_view не знает, что строки больше не существует, и всё также позволяет нам к ней обратиться. Попытка доступа к такой строке через её представление в функции main() приводит к неопределенному поведению, в результате чего мы получаем кракозябры.

Такая же ситуация может произойти и тогда, когда мы создаем объект std::string_view из объекта std::string, а затем модифицируем первоначальный объект std::string. Изменение объекта std::string может привести к созданию в другом месте новой внутренней строки и последующему уничтожению старой. При этом std::string_view продолжит «смотреть» в то место, где была старая строка, но её там уже не будет.

Предупреждение: Следите за тем, чтобы исходная строка, на которую ссылается объект std::string_view, не выходила за пределы области видимости и не изменялась до тех пор, пока используется ссылающийся на нее объект std::string_view.

Конвертация std::string_view в std::string


Объекты класса std::string_view не конвертируются неявным образом в объекты класса std::string, но конвертируются при явном преобразовании:

Результат выполнения программы:

ball
ball

Конвертация std::string_view в строку C-style

Некоторые старые функции (такие как strlen()) работают только со строками C-style. Для того чтобы преобразовать объект класса std::string_view в строку C-style, мы сначала должны конвертировать его в объект класса std::string:

Результат выполнения программы:

ball has 4 letter(s)

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

Функция data()

Доступ к исходной строке объекта std::string_view можно получить при помощи функции data(), которая возвращает строку C-style. При этом обеспечивается быстрый доступ к представляемой строке (как к строке C-style). Но это следует использовать только тогда, когда объект std::string_view не был изменен (например, при помощи функций remove_prefix() или remove_suffix()) и связанная с ним строка имеет нуль-терминатор (так как это строка C-style).

В следующем примере функция std::strlen() ничего не знает о std::string_view, поэтому мы передаем ей функцию str.data():

Результат выполнения программы:

balloon
7

Когда мы пытаемся обратиться к объекту класса std::string_view, который был изменен, функция data() может вернуть совсем не тот результат, который мы ожидали от нее получить. В следующем примере показано, что происходит, когда мы обращаемся к функции data() после изменения представления строки:

Результат выполнения программы:

all has 6 letter(s)
str.data() is alloon
str is all

Очевидно, что данный результат — это не то, что мы планировали увидеть, и он является следствием попытки функции data() получить доступ к данным представления std::string_view, которое было изменено. Информация о длине строки теряется при обращении к ней через функцию data(). std::strlen и std::cout продолжают считывать символы из исходной строки до тех пор, пока не встретят нуль-терминатор, который находится в конце строки baloon.

Предупреждение: Используйте std::string_view::data() только в том случае, если представление std::string_view не было изменено и отображаемая строка содержит завершающий нулевой символ (нуль-терминатор). Использование функции std::string_view::data() со строкой без нуль-терминатора чревато возникновением ошибок.

Нюансы std::string_view

Будучи относительно недавним нововведением, класс std::string_view реализован не так уж и идеально, как хотелось бы:

Нет никаких причин для неработоспособности строк №5-6, но тем не менее они не работают. Вероятно, полная поддержка данного функционала будет реализована в следующих версиях стандарта C++.

Оценить статью:

Звёзд: 1Звёзд: 2Звёзд: 3Звёзд: 4Звёзд: 5 (168 оценок, среднее: 4,86 из 5)
Загрузка...

Комментариев: 8

  1. Пирожок:

    Шел 2021 год, а в С++ строках std::string все так же нет поддержки юникодных символов (попробуйте поменять регистр не латинского текста вылезет кучу проблем). Нет хотябы базового функционала который есть в строках на том же Python.

    Вместо того чтобы доводить до ума один из важнейших кирпичиков языка, мы получили "более лучший" std::string_view который делает все тоже что и нормальная строка, но при этом не хранит ее значение, то есть мы в коде обязаны иметь запасную строку чтобы string_view работал.

    При этом согласно все тому же С++ 17, std::string больше не копируется и автоматически используется move, то есть нужды использовать некоторый "быстрый" придаток к строке еще меньше.

    И все это сделано в угоду некоторой "производительности". Но все и так понимают что там где важна производительность никто не будет тащить С++, все пишется на чистом быстром C где все отточено до скорости "портативного ассемблера".

    1. Владимир:

      Для юникодных строк, вроде, есть wstring, u16string и u32string.

  2. neko:

    А в чем разница между использованием const std::string & и std::string_view?

    1. Арсений:

      Представьте, что вы передаете в функцию в качестве параметра string_view литерал в двойных кавычках (char*, временный объект). В случае string произойдет копирование во временный объект типа string за линейное время от длины строки, а в случае со string_view это почти бесплатно – string_view внутри себя хранит только сам указатель, а не массив типа char на куче, например. Но тут тоже, как я предполагаю, придется пройтись по всей строке и посчитать ее длину. Все-таки это сильно эффективнее по памяти, если строка очень длинная. Один указатель и число против огромного массива.

      1. neko:

        Понятно, спасибо

  3. Сергій:

    Очень полезно что автор знакомит с разными стандартами, и плюсами\минусами новых стандартов.

  4. Codder:

    "Нет никаких причин на то, почему бы строки №5-6 были бы неработоспособными, но, тем не менее, они не работают. Вероятно, полная поддержка данного функционала будет реализована в следующих версиях стандарта C++."

    Прочитать, чтобы понять — что std::string_view пока что фуфло. И лучше использовать обычный string, поскольку ни одного преимущества выявить не удалось.

    1. foo:

      imho, все на производительность заточено. по сути std::string покрывает все потребности, но это неэффективно в некоторых случаях … очень _не_ эффективно. с++ с этим не может мириться (с такими накладными расходами по памяти и процессору) и вот … частное решение std::string_view, которое частично (т.е. если задача позволяет) эту проблему хоть как-то сглаживает.

Добавить комментарий для foo Отменить ответ

Ваш E-mail не будет опубликован. Обязательные поля помечены *