为什么 std::string_view::data 不包含空终止符?
Why does std::string_view::data not include a null terminator?
这段代码有未定义的行为:
#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
?这可能会更快,但我没有测量。
无论哪种方式,每次我想将 const char*
传递给只接受 const char*
的函数时都必须构造一个 std::string
有点不必要,但必须成为委员会以这种方式决定的原因。
那么,为什么 std::string_view::data
不是 return 像 std::string::data
这样的以 null 结尾的字符串?
So, why does std::string_view::data not return a null-terminated
string like std::string::data
只是因为它不能。 string_view
可以是对较大字符串(字符串的子字符串)的较窄视图。这意味着字符串 viewed 不必在特定视图的末尾具有空终止符。由于显而易见的原因,您不能将空终止符写入底层字符串,并且您不能在没有内存泄漏的情况下创建字符串和 return char *
的副本。
如果您想要一个空终止字符串,则必须从中创建一个 std::string
副本。
让我展示一下 std::string_view
:
auto tokenize(std::string_view str, Pred is_delim) -> std::vector<std::string_view>
这里生成的向量包含标记作为对较大字符串的视图。
string_view
的目的是成为一个表示连续字符序列的范围。将这样的范围限制为以 NUL-terminator 结尾的范围会限制 class.
的有用性
话虽这么说,但使用 string_view
的替代版本仍然有用,它仅用于从真正 NUL-terminated.
的字符串创建
我的zstring_view
class是私有继承自string_view
的,它提供了对去掉前面元素等不能使字符串非[=27=的操作的支持].它提供了其余的操作,但它们 return 一个 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");
这段代码有未定义的行为:
#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
?这可能会更快,但我没有测量。
无论哪种方式,每次我想将 const char*
传递给只接受 const char*
的函数时都必须构造一个 std::string
有点不必要,但必须成为委员会以这种方式决定的原因。
那么,为什么 std::string_view::data
不是 return 像 std::string::data
这样的以 null 结尾的字符串?
So, why does std::string_view::data not return a null-terminated string like std::string::data
只是因为它不能。 string_view
可以是对较大字符串(字符串的子字符串)的较窄视图。这意味着字符串 viewed 不必在特定视图的末尾具有空终止符。由于显而易见的原因,您不能将空终止符写入底层字符串,并且您不能在没有内存泄漏的情况下创建字符串和 return char *
的副本。
如果您想要一个空终止字符串,则必须从中创建一个 std::string
副本。
让我展示一下 std::string_view
:
auto tokenize(std::string_view str, Pred is_delim) -> std::vector<std::string_view>
这里生成的向量包含标记作为对较大字符串的视图。
string_view
的目的是成为一个表示连续字符序列的范围。将这样的范围限制为以 NUL-terminator 结尾的范围会限制 class.
话虽这么说,但使用 string_view
的替代版本仍然有用,它仅用于从真正 NUL-terminated.
我的zstring_view
class是私有继承自string_view
的,它提供了对去掉前面元素等不能使字符串非[=27=的操作的支持].它提供了其余的操作,但它们 return 一个 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");