【问题标题】:std::string_view complexity for constant strings常量字符串的 std::string_view 复杂度
【发布时间】:2026-02-10 23:30:02
【问题描述】:

我正在开发包含大量静态字符串的库。为了稍微优化运行时,这应该从以空结尾的字符串(经典的 C 样式字符数组)更改为预先知道长度的结构,例如 std::stringstd::string_view 或自定义指针+长度对。 std::string 众所周知且用途广泛,但如果 Small-String-Optimization 不适用,它的缺点是占用空间大(x64 上至少 32 位)加上 VM 开销(堆和运行时成本),这会影响我的大部分数据。 string_view 听起来是最好的选择,只要我可以确定数据是空终止的(尽管 string_view 本身没有这样的保证,但可以通过约定来减轻风险)。

仍然很重要的问题:现代编译器是否在不使用内部 strlen 的情况下预初始化 string_view,即具有恒定复杂性而不是 O^(n)?如果有,是否保留空终止符?

当然,我可以添加一些宏来初始化string_views,但这会很麻烦。比如:

#define foo(buf) foo, (sizeof(foo)-1)
const string_view svMyVar(foo("original C-string value"));

我希望编译器会为我简化。

【问题讨论】:

  • 您的意思是写#define foo(buf) buf, (sizeof(buf)-1) 吗?
  • 您也可以存储为char const str[] = "foobar";,让编译器确定数组的大小,您可以通过类型系统查询。这在理论上允许 ctor string_view(char(&)[N]) 保证在编译时计算字符串“长度”,但标准库中没有这样的 ctor。
  • 除此之外,还有"foobar"sv 使用用户定义的文字,编译器再次知道字符串长度并将其传递给函数operator""(char const*, size_t)。最重要的是,您可以拥有constexpr string_view x = ...;,它应该由任何合理的优化编译器在编译时初始化。
  • 你可以在这里玩一下:compiler-explorer.com/z/bc6s847Pr 正如预期的那样,constexpr 导致编译时字符串长度“计算”,即使没有优化,可变块范围 string_view 得到他们的字符串长度计算在编译时已在-O1 解决。使用 sv 用户定义的文字也可以在编译时解析长度而不进行优化。

标签: c++


【解决方案1】:

虽然std::string_view(const char*)构造函数具有线性复杂度,但它是constexpr构造函数并且字符串字面量是编译时常数,而优化器在实践中通常能够在编译时执行线性复杂度,使运行时常数。在不断评估的情况下,这是有保证的。

请注意,您建议的宏以及其他 answer 中建议的模板的行为与 std::string_view(const char*) 不同,因为字符串文字可能包含空终止符,并且构造函数仅延伸到第一个终止符,而宏则延伸整个字面意思。

如果与包含具有垃圾值的未初始​​化元素的非字符串文字数组一起使用,这尤其成问题:

const string_view svMyVar1("test\0test");
// svMyVar1.size() == 4

const string_view svMyVar2(foo("test\0test"));
// svMyVar2.size() == 9

char arr[32];
arr[0] = 'a';
arr[1] = '\0';
// arr now contains a null terminated "a" followed by 30 garbage chars
const string_view svMyVar3(foo(arr));
// svMyVar3.size() == 31, contains garbage

如果有,是否保留空终止符?

字符串视图不会修改它所引用的数组。如果引用的数组在引用的字符串之后包含一个空终止符,则空终止符保留在那里。如果没有空终止符,则不添加空终止符。

即使在视图边界之外存在空终止符,读取 svMyVar[svMyVar.size()] 仍然具有未定义的行为。

另一方面,如果您知道那里有一个空终止符(或任何其他字符),那么阅读 *(svMyVar.data() + svMyVar.size()) 就可以了。您不能依赖字符串视图的一般情况,但如果视图是从保证为空终止的字符串文字创建的,则可以依赖它。


不使用内部 strlen

编译器足够聪明,甚至可以在编译时计算 strlen("literal")

从技术上讲,std::string_view 在内部使用 Traits::length,而不是 std::strlen

【讨论】:

  • 我知道在 null 终止符是 char 数组的一部分的情况下可能会出现不同的行为,但这肯定不是这里要关注的主题。我已经看到文档中某处提到的文字,但从未完全确定它们应该如何工作 - 我可以想象一个编译器会按摩数据以进行优化,剥离终止符,因此是我的问题。
  • @PasterOfMuppets 那么我对你的回答中最有用的部分是“保证为空终止的字符串文字”
【解决方案2】:

不幸的是,std::string_view 没有构造函数采用 const char (&) [N],而只有(与 const char* 相关)const char*const char*, std::size_t size。 前者要计算长度,后者是给定的。

你可能有函数而不是宏

template <std::size_t N>
std::string_view make_string_view(const char* (&s)[N]) { return {s, N - 1}; }

operator ""sv 甚至可能更简单(大小也是已知的/在编译时用于构造string_view):

  • "hello world"sv

请注意,如果需要,您可以包含 \0

  • "hello world\0"sv

【讨论】: