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

ada*_*603 11 c++ string c-strings stdstring string-literals

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

auto str = "Hello World!"s; // RHS is equivalent to: std::string{ "Hello World!" }
Run Code Online (Sandbox Code Playgroud)

当然,构建一个未命名的std::string对象可以在C++ 14之前完成,但由于C++ 14的方式简单得多,我认为实际上会有更多的人考虑std::string在现场构建对象,这就是为什么我认为有必要提问这个.

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


例1:

考虑以下:

void foo(std::string arg);

foo("bar");  // option 1
foo("bar"s); // option 2
Run Code Online (Sandbox Code Playgroud)

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

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


例2:

考虑以下:

std::cout << "Hello World!" << std::endl;  // option 1
std::cout << "Hello World!"s << std::endl; // option 2
Run Code Online (Sandbox Code Playgroud)

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


当然,在所有情况下,构造std::string对象都可能导致堆积分配,这可能会引发,因此也应考虑异常安全性.这在第二个例子中更是一个问题,但是在第一个例子中,std::string无论如何都会在两种情况下构造一个对象.实际上,从构造字符串对象中获取异常是不太可能的,但在某些情况下仍然可能是有效的参数.

如果你能想到更多的例子,请在答案中加入.我对有关未命名std::string对象的使用的一般建议感兴趣,而不仅仅是这两种特殊情况.我只是将这些内容包括在内,以指出我对这个主题的一些看法.

此外,如果我出错了,请随意纠正我,因为我不是一个C++专家.我描述的行为只是我对事情如何运作的猜测,而我并没有将它们建立在真正的研究或实验上.

Ton*_*roy 3

std::string在什么情况下,构造一个未命名的对象而不是简单地使用 C 风格的字符串文字是一个好(或坏)的想法?

std::string当您特别想要一个类型的变量时,std::string无论是用于

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

  • 更丰富、直观且不易出错的界面(值语义、迭代器findsize

    • 值语义意味着==、复制等对值进行操作,这与 C 字符串文字衰减到s<之后的指针/引用语义不同const char*
  • 调用some_templated_function("123"s)将简洁地确保<std::string>实例化,并且能够在内部使用值语义来处理参数

    • 如果您知道其他代码正在实例化模板std::string,并且相对于您的资源限制而言它非常复杂,您可能需要传递一个std::string太以避免不必要的实例化const char*,但很少需要关心
  • 包含嵌入NULs的值

在以下情况下可能会首选 C 样式字符串文字:

  • 需要指针式语义(或者至少不是问题)

  • 该值只会传递给期望的函数const char*,或者std::string无论如何都会构造临时值,并且您不关心您是否给编译器优化器一个额外的障碍,以实现编译或加载时构造(如果有可能重用该值)相同的std::string实例(例如,当通过const引用传递给函数时)-同样很少需要关心。

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

    • 您无法真正从std::string .data()/获得相同的内容,因为在程序执行期间.c_str()相同的地址可能与不同的文本(和不同的实例)相关联,并且不同地址的缓冲区可能包含相同的文本std::stringstd::string
  • std::string在 a离开作用域并被销毁后,让指针保持有效(例如给定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 字符串文字的编译时串联(例如,"abc" "xyz"成为一个连续的const char[]文字"abcxyz") - 这在宏替换中特别有用

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

讨论

[basic.string.literals] 21.7 列出:

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

返回: string{str,len}

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

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

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

你选择哪一个并不重要。维护方面 - 如果foo(const std::string&)更改为foo(const char*),则只有foo("xyz");调用才会无缝地继续工作,但几乎没有什么模糊的合理原因(所以 C 代码也可以调用它? - 但如果不继续这样做,仍然会有点疯狂为现有客户端代码提供foo(const std::string&)重载;因此可以用 C 实现? - 也许;消除对<string>标头的依赖? - 与现代计算资源无关)。

std::cout << "你好世界!" << std::endl; // 选项1

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

前者将调用operator<<(std::ostream&, const char*),直接访问常量字符串文字数据,唯一的缺点是流可能必须扫描终止 NUL。“选项 2”将匹配const-reference 重载并意味着临时的构造,尽管编译器可能能够对其进行优化,因此它们不会经常不必要地这样做,甚至可以在编译时有效地创建字符串对象(这可能只是对于足够短以使用对象内短字符串优化 (SSO) 方法的字符串来说很实用)。如果他们还没有进行此类优化,那么潜在的好处以及这样做的压力/愿望可能会增加。