Почему std::string_view::data не содержит нулевого терминатора?

Этот код имеет неопределенное поведение:

#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?

2 ответа

Решение

Итак, почему 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и предоставляет поддержку для удаления элементов из front и других операций, которые не могут сделать строку без 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;
};

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

template < size_t L > std::string_view string_viewz(const char (&t) [L])
{
    return std::string_view(t, L);
}

Цель здесь не в том, чтобы попытаться исправить проблему совместимости, их слишком много. Но если вы знаете, что делаете, когда хотите, чтобы диапазон string_view имел нулевое значение (сериализация), тогда это хороший трюк.

auto view = string_viewz("Surrogate String");
Другие вопросы по тегам