字符串文字的 C++ constexpr std::array

C++ constexpr std::array of string literals

一段时间以来,我一直很高兴地在我的代码中使用以下样式的常量字符串文字,但并没有真正理解它是如何工作的:

constexpr std::array myStrings = { "one", "two", "three" };

这可能看起来微不足道,但我对幕后发生的事情的细节一无所知。根据我的理解,class 模板参数推导 (CTAD) 用于构造适当大小和元素类型的数组。我的问题是:

  1. 在这种情况下 std::array 的元素类型是什么,或者这个实现是特定的?查看调试器(我使用的是 Microsoft C++),元素只是指向非连续位置的指针。
  2. 以这种方式声明字符串文字的 constexpr 数组是否安全?

我可以改为这样做,但它不是那么整洁:

const std::array<std::string, 3> myOtherStrings = { "one", "two", "three" };

是的,这是CTAD为您推导模板参数。 (C++17 起)

std::array 有一个 deduction guide 可以使用这种形式的初始值设定项启用 CTAD。

它将myStrings的类型推断为

const std::array<const char*, 3>

const char* 是通常 array-to-pointer 衰减应用于初始化列表元素(const chars 的数组)的结果。

前面的

constconstexpr.

的结果

数组的每个元素将指向相应的字符串文字。

constexpr 是安全的,您可以像通过 const char* 指针使用单个字符串文字一样使用数组元素。特别是尝试通过 const_cast 修改这些文字或数组将有未定义的行为。

const std::array<std::string, 3> 也可以,但不能用于常量表达式。 constexpr 不允许,因为 std:string

CTAD 也可以在字符串文字运算符的帮助下推断出这种类型:

#include<string>
using namespace std::string_literals;

//...

const std::array myOtherStrings = { "one"s, "two"s, "three"s };

或自 C++20 起:

const auto myOtherStrings = std::to_array<std::string>({ "one", "two", "three" });

作为 ,原始代码的类型推导产生 const std::array<const char*, 3>。这有效,但它不是 C++ std::string,因此每次使用都需要扫描 NUL 终止符,并且它们不能包含嵌入的 NUL。我只是想强调我评论中关于使用 std::string_view.

的建议

由于 std::string 固有地依赖于 run-time 内存分配,除非相关代码的 entirety 也是 [=17],否则您不能使用它=](所以在 run-time 处根本不存在实际的 string,编译器在 compile-time 处计算最终结果),如果目标是避免不必要的,这不太可能对你有帮助运行时为编译时部分已知的东西工作(特别是如果 array 在每次函数调用时被重新创建;它不是全局的或 static,所以它完成了很多次,而不仅仅是在使用前初始化一次) .

就是说,如果您可以依赖 C++17,则可以与 std::string_view 平分秋色。它有一个非常简洁的文字形式(添加 sv 作为任何字符串文字的前缀),并且它完全是 constexpr,所以通过这样做:

// Top of file
#include <string_view>
// Use one of your choice:
using namespace std::literals; // Enables all literals
using namespace std::string_view_literals; // Enables sv suffix only
using namespace std::literals::string_view_literals; // Enables sv suffix only

// Point of use
constexpr std::array myStrings = { "one"sv, "two"sv, "three"sv };

你得到的东西不涉及运行时工作,具有 std::string 的大部分优点(知道它自己的长度,可以包含嵌入的 NULs,被大多数人接受 string-oriented APIs),因此对于函数接受字符串数据的三种常见方式,比 C-style 字符串更有效:

  1. 对于需要读取 string-like 事物的现代 APIs,它们按值接受 std::string_view 并且开销只是将指针和长度复制到函数
  2. 对于接受 const std::string& 的旧 API,它会在您调用它时构造一个临时的 std::string,但它可以使用从 [=15] 中提取长度的构造函数=] 所以它不需要用 strlen 预先遍历 C-style 字符串来计算分配多少。
  3. 对于任何需要 std::string 的 API(因为它将 modify/store 自己的副本),他们按值接收 string,而你得到与 #2 相同的好处(必须构建,但构建效率更高)。

使用 std::string_view 比使用 std::string 做得更差的唯一情况是情况 #2(如果 std::array 包含 std::string,则没有副本会发生),如果你打了几次这样的电话,你只会输在那里;在那种情况下,您只需咬紧牙关并使用 const std::array myStrings = { "one"s, "two"s, "three"s };,支付较小的运行时成本来构建真正的 strings,以换取在传递给 old-style [=70= 时避免复制]正在服用 const std::string&