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

时间:2015-08-20 10:58:34

标签: c++ string stdstring string-literals c-strings

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

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

当然,构建一个未命名的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

如果我正确,第一种方法将调用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

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

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

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

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

3 个答案:

答案 0 :(得分:3)

  

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

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

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

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

    • 值语义表示==<复制等处理值,不像C字符串文字衰减到{{1后的指针/按引用语义}}Š
  • 调用const char*将简明扼要地确保some_templated_function("123"s)实例化,并且可以使用内部的值语义来处理该参数

    • 如果你知道其他代码实例化了<std::string>的模板,并且它相对于你的资源限制有很大的复杂性,你可能也希望传递一个std::string避免对std::string进行不必要的实例化,但很少需要关注
  • 包含嵌入式const char* s

  • 的值

可能首选C风格的字符串文字:

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

  • 价值只会传递给期待NUL的函数,或者const char*临时版本无论如何都会被构建而你并不关心你&#39 ;如果有可能重用相同的std::string实例(例如,通过std::string - 参考时传递给函数),那么为编译器优化器提供一个额外的障碍来实现编译或加载时间构造 - 再次,它很少需要照顾。

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

    • 您可以从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(); }总是使用动态分配过早的悲观主义(没有SSO,或者更长的文本)。

  • 您正在利用相邻C字符串文字的编译时连接(例如std::string str(My_Enum e) { return e == Zero ? "0"s : "1"s; }成为一个连续的"abc" "xyz"字面值const char[]) - 这在内部特别有用宏观替代

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

讨论

[basic.string.literals] 21.7列出:

  

"abcxyz"

     

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

基本上,使用string{str,len}调用一个按值返回""s的函数 - 关键的是,您可以绑定std::string引用或右值引用,但不能绑定左值引用。< / p>

当用于致电const时,void foo(std::string arg);确实将 移动 构建。

  

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

你选择的并不重要。明智的维护 - 如果arg更改为foo(const std::string&),则只有foo(const char*)次调用将无缝继续工作,但非常可能有一些模糊的原因(所以C代码也可以调用它? - 但是仍然有点疯狂不继续为现有客户端代码提供foo("xyz");重载;所以它可以在C中实现? - 也许;删除依赖在foo(const std::string&)标题上? - 与现代计算资源无关)。

  

std :: cout&lt;&lt; &#34; Hello World!&#34; &LT;&LT;的std :: ENDL; //选项1

     

std :: cout&lt;&lt; &#34; Hello World!&#34; s&lt;&lt;的std :: ENDL; //选项2

前者将调用<string>,直接访问常量字符串文字数据,唯一的缺点是流式传输可能必须扫描终止NUL。 &#34;选项2&#34;将匹配operator<<(std::ostream&, const char*) - 引用重载并暗示构造临时,但编译器可能能够优化它,以便它们不会经常不必要地执行此操作,甚至在编译时有效地创建字符串对象(可能只适用于足够短的字符串,以使用对象内短字符串优化(SSO)方法。如果他们已经没有进行这样的优化,那么这样做的潜在好处和压力/愿望可能会增加。

答案 1 :(得分:1)

首先,我认为答案是基于意见的!

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

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

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

对于你的例子2我倾向于使用&#34; old&#34; c-string版本,因为生成std :: string对象有开销。给cout的字符串指针是明确定义的,我看到没有用例可以带来一些好处。

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

使用&#39; -literal正在使用,我需要定义它是一个std :: string。

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

仅作为评论:我必须在非常小的嵌入式设备上进行大量编程,特别是在8位AVR上。使用std :: string会导致很多开销。如果我必须使用动态容器,因为我需要这个容器的功能,那么有一个非常好的实现和测试。但如果我不需要它,使用它只是昂贵。

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

只有我的两分钱!

答案 2 :(得分:1)

  

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

什么是好主意往往随着情况而变化。

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

  

当然,在所有情况下,构造一个std :: string对象都可能导致堆分配,这可能会抛出,因此也应该考虑异常安全性。

呃......虽然代码确实可以抛出,但这种情况无关紧要,除非在非常特殊的情况下(例如,嵌入式代码运行 - 或接近 - 硬件的内存限制,或高可用性应用程序/环境)

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

总之,不必担心来自实例化std :: string literal 的内存不足情况的异常安全性。如果您遇到内存不足的情况,请在发现错误时更改代码。