Уже было изрядное количество обреченных на std::string_view
. Это опасно по своей сути, и люди правы, когда делают паузу. Я думаю, что правильно рассматривать его как инструмент для оптимизации, и это связано с существенной и вечной оговоркой, которую Дональд Кнут сформулировал так:
Настоящая проблема заключается в том, что программисты тратят слишком много времени на беспокойство об эффективности не в том месте и не в то время; преждевременная оптимизация — корень всех зол (или, по крайней мере, большей их части) в программировании.
Поэтому я думаю, что лучший урок здесь — не используйте std::string_view
, если он вам не нужен, и даже тогда помните о потенциальных ловушках. К сожалению, как я выяснил, самый очевидный вариант использования по-прежнему вероломный. Рассмотрим следующую функцию, в которой я получаю очень большую строку — порядка 1 миллиона символов или более — содержащую большую подстроку интересующего меня содержимого.
using std::string; using std::string_view; /* what should this return? */ get_data() { string document = get_really_long_string(); string_view relevant_content = get_relevant_chunk(content); return /* ? */; }
Таким образом, очевидно неправильный ответ заключается в том, что он должен возвращать std::string_view
. Очевидно правильный ответ - просто создать новую строку из строкового представления и вернуть ее. В моем случае подстрока представляла собой почти всю очень длинную строку, я просто вырезал заголовок и пустые строки в конце. Это означает, что я, по сути, выполнял глубокую копию, и это было заметно при использовании программы. Таким образом, хотя возврат std::string
и действителен, он не соответствует моим требованиям. Итак, мы подошли к неочевидно неправильному решению, которое я реализовал первым.
using std::string; using std::string_view; std::tuple<string, string_view> get_data() { string document = get_really_long_string(); string_view relevant_content = get_relevant_chunk(content); return {document, relevant_content}; }
На самом деле, когда я скомпилировал это на clang++, это сработало как шарм! Почему бы и нет? Строка порядка мегабайта почти наверняка находится в куче — в стандарте ничего не указано — поэтому string_view — это просто указатель на фиксированное место в куче, а перемещение базового std::string
— это просто перемещение его дескриптора по стеку. Однако, скомпилированный под g++, я сразу же получил segfaults.
После обсуждения с некоторыми проницательными людьми на #include ‹C++› я указал на часть стандарта C++, которая явно определяет, когда указатели на данные std::string
могут быть признаны недействительными. Для нас актуальна следующая часть:
(4) Ссылки, указатели и итераторы, относящиеся к элементам последовательности basic_string, могут стать недействительными в результате следующих применений этого объекта basic_string:
(4.1) Передача в качестве аргумента любой стандартной библиотечной функции, принимающей в качестве аргумента ссылку на неконстантную базовую_строку.
(4.2) Вызов неконстантных функций-членов, кроме operator[], at, data, front, back, begin, rbegin, end и rend.
Конструктор перемещения, конечно, не является const
, и, таким образом, может сделать недействительными std::string_view
s в std::string
. Это делает приведенный выше код непереносимым и, таким образом, убивает его полезность для меня. Итак, я получил раздражающий, но правильный и переносимый неочевидный правильный ответ:
using /* the good stuff */; tuple<string, ptrdiff_t, size_t> get_data() { string document = get_really_long_string(); string_view relevant_content = get_relevant_chunk(content); ptrdiff_t offset = relevant_content.data() - document.c_string(); size_t length = relevant_content.size(); return {document, offset, length}; }
Который звонящий затем может рекомбинировать в std::string_view
для своих гнусных целей. В моем реальном коде я только что изменил функцию get_relevant_chunk
, чтобы она возвращала size_t lpos
вместо смещения указателя, но идея та же.