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

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

  Обновл. 19 Апр 2020  | 

 2830

В этом уроке мы рассмотрим класс 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 (28 оценок, среднее: 4,86 из 5)
Загрузка...

Добавить комментарий

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