使用 C 风格字符串文字与构造未命名 std::string 对象的默认建议?

Default advice for using C-style string literals vs. constructing unnamed std::string objects?

因此,C++ 14 引入了许多用户定义的字面值供使用,其中之一是 "s" literal suffix,用于创建 std::string 对象。根据文档,它的行为与构造 std::string 对象完全相同,如下所示:

auto str = "Hello World!"s; // RHS is equivalent to: std::string{ "Hello World!" }

当然可以在 C++ 14 之前构造一个未命名的 std::string 对象,但是因为 C++ 14 的方法简单得多,我认为更多的人会真正考虑构造 std::string 对象当场比以前更好,这就是为什么我认为问这个问题是有意义的。

所以我的问题很简单:在什么情况下构建一个未命名的 std::string 对象而不是简单地使用 C 风格的字符串文字是一个好(或坏)主意?


示例 1:

考虑以下几点:

void foo(std::string arg);

foo("bar");  // option 1
foo("bar"s); // option 2

如果我是正确的,第一个方法将调用 std::string 的适当构造函数重载以在 foo 的范围内创建一个对象,第二个方法将构造一个未命名的字符串对象首先,然后从中移动构造 foo 的论点。虽然我确信编译器非常擅长优化这样的东西,但是,第二个版本似乎涉及一个额外的移动,而不是第一个替代方案(当然不像一个移动是昂贵的)。但是同样,在使用合理的编译器编译之后,最终结果很可能是高度优化的,并且没有冗余moves/copies。

此外,如果 foo 被重载以接受右值引用怎么办?在那种情况下,我认为调用 foo("bar"s) 是有意义的,但我可能是错的。


示例 2:

考虑以下几点:

std::cout << "Hello World!" << std::endl;  // option 1
std::cout << "Hello World!"s << std::endl; // option 2

在这种情况下,std::string 对象可能通过右值引用传递给 cout 的运算符,第一个选项可能传递一个指针,所以两者都是非常便宜的操作,但是第二个具有首先构造对象的额外成本。不过,这可能是一种更安全的方式 (?)。


当然,在所有情况下,构造一个 std::string 对象 可能 导致堆分配,可能 抛出,因此也应考虑异常安全性。 这在第二个示例中更像是一个问题,与第一个示例一样,将在中构造一个 std::string 对象无论如何,这两种情况。在实践中,从构造字符串对象中获得异常的可能性很小,但在某些情况下仍然是一个有效的参数。

如果您能想到更多要考虑的示例,请将它们包括在您的答案中。我对关于使用未命名 std::string 对象的一般建议感兴趣,而不仅仅是这两种特殊情况。我只包含这些以指出我对这个主题的一些想法。

此外,如果我有任何错误,请随时纠正我,因为我绝不是 C++ 专家。我描述的行为只是我对事情如何运作的猜测,我并没有将它们建立在实际研究或实验的基础上。

首先我相信答案是基于意见的!

对于您的示例 1,您已经提到了使用新 s 文字的所有重要参数。是的,我希望结果是相同的,所以我认为没有必要在定义中说我想要 std::string。

一个参数可以是,定义了构造函数 explicit 并且不会发生自动类型转换。在这种情况下,s 文字是有帮助的。

但我认为这是一个品味问题!

对于您的示例 2,我倾向于使用 "old" c 字符串版本,因为生成 std::string 对象会产生开销。为 cout 提供指向字符串的指针定义明确,我看不到可以从中受益的用例。

所以我的个人建议实际上是(每天都有新信息可用:-))如果这完全符合我的需要,就使用 c-string。这意味着:字符串是常量,永远不会被复制或修改,只会被使用"as is"。所以 std::string 根本没有任何好处。

并且在我需要定义它的地方使用 's'-literal 是 std::string。

简而言之:如果我不需要 std::string 在旧 c 字符串上提供的附加功能,我就不会使用 std::string。对我来说,重点不是使用 s-literal,而是使用 std::string 与一般的 c-strings。

仅供参考:我必须在非常小的嵌入式设备上进行大量编程,尤其是在 8 位 AVR 上。使用 std::string 会导致大量开销。如果我必须使用动态容器,因为我需要这个容器的功能,那么拥有一个实现和测试得很好的容器是非常好的。但是,如果我不需要它,那么使用它就太昂贵了。

在像 x86 盒子这样的大目标上,std::string 而不是 c 字符串似乎可以忽略不计。但是考虑到小设备可以让您了解大机器上真正发生的事情。

只有我的两分钱!

In what cases it's a good (or bad) idea construct an unnamed std::string object, instead of simply using a C-style string literal?

什么是好主意或坏主意往往因情况而异。

我的选择是只要足够就使用原始文字(只要我不需要文字以外的任何东西)。如果我需要访问除指向字符串第一个元素的指针以外的任何其他内容(字符串长度、返回值、迭代器或其他任何内容),那么我会使用 std::string 文字。

In all cases of course, constructing an std::string object could result in a heap allocation, which could throw, so exception safety should be taken into consideration as well.

呃...虽然代码确实会抛出异常,但这无关紧要,除非在非常特殊的情况下(例如,嵌入式代码 运行 达到或接近硬件的内存限制,或高-可用性 application/environment).

在实践中,我从未因编写 auto a = "abdce"s; 或其他类似代码而遇到内存不足的情况。

总而言之,不要为实例化 std::string 文字 带来的内存不足情况的异常安全而烦恼。如果您在执行此操作时遇到内存不足的情况,请在发现错误时更改代码。

In what cases it's a good (or bad) idea construct an unnamed std::string object, instead of simply using a C-style string literal?

A std::string- 当你特别想要一个 std::string 类型的变量时,文字是个好主意,无论是

  • 稍后修改值(auto s = "123"s; s += '\n';)

  • 更丰富、更直观、更少 error-prone 界面(值语义、迭代器、findsize 等)

    • 值语义 意味着 ==< 复制等值的工作,不像 [=145= 之后的 pointer/by-reference 语义] 文字衰减到 const char*s
  • 调用 some_templated_function("123"s) 将简洁地确保 <std::string> 实例化,参数可以在内部使用值语义进行处理

    • 如果您知道其他代码无论如何都会为 std::string 实例化模板,并且相对于您的资源限制来说它非常复杂,您可能还想传递一个 std::string 以避免对 const char*也有,但很少需要关心
  • 值包含嵌入的 NULs

C-style 字符串文字可能是首选,其中:

  • pointer-style 需要语义(或者至少不是问题)

  • 无论如何,该值只会被传递给期望 const char* 的函数,否则 std::string 临时对象无论如何都会被构造,您不关心您是否正在提供您的如果有可能重用相同的 std::string 实例(例如,当通过 const-reference 传递给函数时),编译器优化器要跨越一个额外的障碍来实现编译或加载时构造 - 同样很少需要关心.

  • (另一个罕见且令人讨厌的 hack)你以某种方式利用了编译器的字符串池行为,例如如果它保证对于任何给定的翻译单元 const char* 到字符串文字只会(但当然总是)如果文本不同

    • 你不能真正从 std::string .data()/.c_str() 得到相同的地址,因为相同的地址可能与不同的文本(和不同的 std::string 实例相关联) 在程序执行期间,std::string 不同地址的缓冲区可能包含相同的文本
  • std::string 离开作用域并被销毁后指针保持有效(例如,给定 enum My_Enum { Zero, One }; - const char* str(My_Enum e) { return e == Zero ? "0" : "1"; } 是安全的,但是 const char* str(My_Enum e) { return e == Zero ? "0"s.c_str() : "1"s.c_str(); } 不是,而且 std::string str(My_Enum e) { return e == Zero ? "0"s : "1"s; } 总是使用动态分配(没有 SSO,或者对于更长的文本)有点过早悲观)

  • 你正在利用相邻 C-string 文字的 compile-time 串联(例如 "abc" "xyz" 变成一个连续的 const char[] 文字 "abcxyz") - 这在宏替换中特别有用

  • 您的内存受限and/or不想在动态内存分配期间冒异常或崩溃的风险

讨论

[basic.string.literals] 21.7 列表:

string operator "" s(const char* str, size_t len);

Returns: string{str,len}

基本上,使用 ""s 是调用一个 returns 按值 std::string 的函数 - 至关重要的是,您可以绑定 const 引用或右值引用,但是不是左值引用。

当用来调用void foo(std::string arg);时,arg确实会被move构造。

Also, what if foo is overloaded to accept rvalue references? In that case, I think it would make sense to call foo("bar"s), but I could be wrong.

选择哪个并不重要。明智的维护——如果 foo(const std::string&) 被更改为 foo(const char*),只有 foo("xyz"); 次调用会无缝地继续工作,但是有 非常 几个模糊的似是而非的原因可能是(所以 C 代码也可以调用它? - 但如果不继续为现有客户端代码提供 foo(const std::string&) 重载仍然有点疯狂;所以它可以在 C 中实现? - 也许;删除对 <string> header 的依赖?- 与现代计算资源无关)。

std::cout << "Hello World!" << std::endl; // option 1

std::cout << "Hello World!"s << std::endl; // option 2

前者会调用operator<<(std::ostream&, const char*),直接访问常量字符串文字数据,唯一的缺点是流式处理可能需要扫描终止NUL。 “选项 2”将匹配 const-reference 重载并暗示构建一个临时的,尽管编译器可能能够对其进行优化,因此他们不会经常这样做,甚至有效地创建字符串 object 在编译时(这可能只适用于短到足以使用 in-object 短字符串优化 (SSO) 方法的字符串)。如果他们还没有进行此类优化,那么这样做的潜在收益和 pressure/desire 可能会增加。