8

此代码具有未定义的行为:

#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为 astd::string呢?这可能会更快,但我没有测量。

无论哪种方式,std::string每次我想将 a 传递const char*给只接受 a 的函数时都必须构造 aconst char*有点不必要,但委员会这样决定肯定是有原因的。

那么,为什么不std::string_view::data返回一个以 null 结尾的字符串std::string::data呢?

4

3 回答 3

19

那么,为什么 std::string_view::data 不返回像 std::string::data 这样的空终止字符串

很简单,因为做不到。Astring_view可以是更大字符串(字符串的子字符串)的狭义视图。这意味着查看的字符串不必在特定视图的末尾具有空终止符。出于显而易见的原因,您不能将空终止符写入底层字符串,并且您不能创建字符串的副本并在char *没有内存泄漏的情况下返回。

如果您想要一个空终止字符串,则必须从中创建一个std::string副本。

让我展示一个很好的用法std::string_view

auto tokenize(std::string_view str, Pred is_delim) -> std::vector<std::string_view>

这里生成的向量包含标记作为对较大字符串的视图。

于 2017-01-18T14:29:41.680 回答
13

的目的string_view是成为表示连续字符序列的范围。将这样的范围限制为以 NUL 终止符结尾的范围会限制类的有用性。

string_view话虽如此,拥有一个仅旨在从真正以 NUL 终止的字符串创建的替代版本仍然很有用。

我的zstring_view类是从 私有继承的string_view,它支持从前面删除元素和其他不能使字符串非 NUL 终止的操作。它提供了其余的操作,但它们返回 a string_view,而不是 a 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;
};
于 2017-01-18T14:54:06.220 回答
1

在处理带有已知 null 终止符的字符串文字时,我通常使用类似这样的方法来确保 null 包含在计数字符中。

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

这里的目的不是试图解决兼容性问题,太多了。但是,如果您知道自己在做什么,希望 string_view 跨度为 null ( Serialization ),那么这是一个不错的技巧。

auto view = string_viewz("Surrogate String");
于 2020-03-31T20:09:26.957 回答