Этот код имеет неопределенное поведение:
#include <string_view>
#include <iostream>
using namespace std::string_view_literals;
void foo(std::string_view msg) {
std::cout << msg.data() << '\n'; // undefined behavior if 'msg' is not null-
// terminated
// std::cout << msg << '\n'; is not undefined because operator<< uses
// iterators to print 'msg', but that's not the point
}
int main() {
foo("hello"sv); // not null-terminated - undefined behavior
foo("foo"); // same, even more dangerous
}
Причина в том что std::string_view
может хранить строки с ненулевым символом в конце и не включает в себя нулевой терминатор при вызове data
, Это действительно ограничивает, так как для приведенного выше кода, определяющего поведение, я должен построить std::string
из этого:
std::string str{ msg };
std::cout << str.data() << '\n';
Это действительно делает std::string_view
в этом случае нет необходимости, мне все еще нужно скопировать строку, переданную foo
так почему бы не использовать семантику перемещения и изменения msg
к std::string
? Это может быть быстрее, но я не измерял.
В любом случае, необходимо построить std::string
каждый раз, когда я хочу передать const char*
к функции, которая принимает только const char*
это немного излишне, но должна быть причина, почему Комитет решил это так.
Итак, почему std::string_view::data
не возвращать строку с нулевым символом в конце, например std::string::data
?
Итак, почему std :: string_view :: data не возвращает нулевое окончание
строка типа std :: string :: data
Просто потому, что не может. string_view
может быть более узким видом на большую строку (подстроку строки). Это означает, что строка Количество просмотров не обязательно будет иметь нулевое завершение в конце определенного представления. Вы не можете записать нулевой терминатор в основную строку по понятным причинам, и вы не можете создать копию строки и вернуть char *
без утечки памяти.
Если вы хотите завершить нулевую строку, вам нужно создать std::string
скопировать из него.
Позвольте мне показать хорошее использование std::string_view
:
auto tokenize(std::string_view str, Pred is_delim) -> std::vector<std::string_view>
Здесь результирующий вектор содержит токены как представления в большей строке.
Цель string_view
должен быть диапазоном, представляющим непрерывную последовательность символов. Ограничение такого диапазона диапазоном, заканчивающимся NUL-ограничителем, ограничивает полезность класса.
При этом было бы полезно иметь альтернативную версию string_view
который предназначен только для того, чтобы быть созданным из строк, которые действительно заканчиваются NUL.
мой zstring_view
класс унаследован от string_view
и предоставляет поддержку для удаления элементов с фронта и других операций, которые не могут сделать строку не завершенной NUL. Это обеспечивает остальные операции, но они возвращают string_view
не zstring_view
,
Вы будете удивлены тем, как мало операций вы потеряете от string_view
чтобы сделать эту работу:
template<typename charT, typename traits = std::char_traits<charT>>
class basic_zstring_view : private basic_string_view<charT, traits>
{
public:
using base_view_type = basic_string_view<charT, traits>;
using base_view_type::traits_type;
using base_view_type::value_type;
using base_view_type::pointer;
using base_view_type::const_pointer;
using base_view_type::reference;
using base_view_type::const_reference;
using base_view_type::const_iterator;
using base_view_type::iterator;
using base_view_type::const_reverse_iterator;
using base_view_type::reverse_iterator;
using typename base_view_type::size_type;
using base_view_type::difference_type;
using base_view_type::npos;
basic_zstring_view(const charT* str) : base_view_type(str) {}
constexpr explicit basic_zstring_view(const charT* str, size_type len) : base_view_type(str, len) {}
constexpr explicit basic_zstring_view(const base_view_type &view) : base_view_type(view) {}
constexpr basic_zstring_view(const basic_zstring_view&) noexcept = default;
basic_zstring_view& operator=(const basic_zstring_view&) noexcept = default;
using base_view_type::begin;
using base_view_type::end;
using base_view_type::cbegin;
using base_view_type::cend;
using base_view_type::rbegin;
using base_view_type::rend;
using base_view_type::crbegin;
using base_view_type::crend;
using base_view_type::size;
using base_view_type::length;
using base_view_type::max_size;
using base_view_type::empty;
using base_view_type::operator[];
using base_view_type::at;
using base_view_type::front;
using base_view_type::back;
using base_view_type::data;
using base_view_type::remove_prefix;
//`using base_view_type::remove_suffix`; Intentionally not provided.
///Creates a `basic_string_view` that lacks the last few characters.
constexpr basic_string_view<charT, traits> view_suffix(size_type n) const
{
return basic_string_view<charT, traits>(data(), size() - n);
}
using base_view_type::swap;
template<class Allocator = std::allocator<charT> >
std::basic_string<charT, traits, Allocator> to_string(const Allocator& a = Allocator()) const
{
return std::basic_string<charT, traits, Allocator>(begin(), end(), a);
}
constexpr operator base_view_type() const {return base_view_type(data(), size());}
using base_view_type::to_string;
using base_view_type::copy;
using base_view_type::substr;
using base_view_type::operator==;
using base_view_type::operator!=;
using base_view_type::compare;
};