该不该用 std::string_view 替代 const std::string&?

问题

  • 有的大佬建议使用 std::string_view 替代 const std::string&,也有的大佬表示反对,到底该不该呢?

  • 以下代码运行后显示什么?

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <string_view>

using namespace std::string_view_literals;

int main() {
auto s = "Hello C++ 20!"sv.substr(0, 5);
printf("%.*s\n", static_cast<int>(s.size()), s.data());
std::cout << s << '\n';
printf("%s\n", s.data()); // buggy
}

分析

反对者可能是 C 语言的受害者!(这里没说反对得不对哦!)

先看看 CppCoreGuidelines 的说法:

In C++17, we might use string_view as the argument, rather than const string& to allow more flexibility to callers

这里说的是使用 std::string_view 作为参数,能够适配更多的调用者。不管传入 const char* 类型,还是 std::string 类型的变量,都能编译。

反之,如果参数是 const std::string& 类型,则有以下缺点:

  • 传入 std::string_view 类型无法编译。

  • 传入 const char* 类型,会多构造一个 std::string 类型的临时变量。

但是 CppCoreGuidelines 并没有建议把 const std::string& 参数都替换成 std::string_view,而是说字符串是一个大话题,需要分很多种情况讨论。具体可见 SL.str: String 节,下面列出大概意思:

Text manipulation is a huge topic.
std::string doesn’t cover all of it.
This section primarily tries to clarify std::string’s relation to char*, zstring, string_view, and gsl::span<char>.
The important issue of non-ASCII character sets and encodings (e.g., wchar_t, Unicode, and UTF-8) will be covered elsewhere.

  • SL.str.1: Use std::string to own character sequences
  • SL.str.2: Use std::string_view or gsl::span<char> to refer to character sequences
  • SL.str.3: Use zstring or czstring to refer to a C-style, zero-terminated, sequence of characters
  • SL.str.4: Use char* to refer to a single character
  • SL.str.5: Use std::byte to refer to byte values that do not necessarily represent characters
  • SL.str.10: Use std::string when you need to perform locale-sensitive string operations
  • SL.str.11: Use gsl::span<char> rather than std::string_view when you need to mutate a string
  • SL.str.12: Use the s suffix for string literals meant to be standard-library strings

那么,究竟啥场景能用 std::string_view 类型参数?

F.25: Use a zstring or a not_null<zstring> to designate a C-style string 节有一句话:

If you don’t need null termination, use string_view.

这句是重点!因为 std::string_view 并不保证 Null-terminated,如果函数使用 std::string_view 参数,却做了 Null-terminated 的假定,那么,在把参数传给其它 C-Style API 时,就可能导致 bug,甚至崩溃。

例如,这位大佬的文章说的情况:

简单地说,std::string 是保证 Null-terminated 的,而 std::string_view 不保证,当 const std::string& 参数被替换为 std::string_view 时,对于部分没有 Null-terminated 的字符序列,C-Style API 可能读入超过范围的字符,而导致逻辑上的错误,还可能因为读到无权限的地址而崩溃。

总结

简单地说,看情况而定。总之,为了写高质量的 C++ 代码,必须细分这么多的情况。

但您如果熟悉 C 语言,可能会提出疑问:大部分 CRT 函数或 C-Style API 不都做了字符串是 Null-terminated 的假定?确实如此!如果人人写好注释,人人遵守规则,那确实能把代码简化。问题是:异常和意外,哪个先来?

如果您使用微信,也可以关注公众号 UMU618,在公众号文章里评论。