使用c风格字符串字面值与构造未命名std::string对象的默认建议
Default advice for using C-style string literals vs. constructing unnamed std::string objects?
所以c++ 14引入了一些用户定义的文字来使用,其中一个是"s"字面值后缀,用于创建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++专家。我所描述的行为只是我对事物如何运作的猜测,我并没有基于实际的研究或实验。
在什么情况下,它是一个好(或坏)的主意构造一个未命名的
std::string
对象,而不是简单地使用c风格的字符串文字?
当你特别想要一个std::string
类型的变量时,std::string
字面量是一个好主意,无论对于
-
修改后值(
auto s = "123"s; s += 'n';
) -
更丰富、更直观、更少出错的接口(值语义、迭代器、
find
、size
等)-
值语义意味着
==
,<
复制等在值上工作,不像c字符串字面量衰减到const char*
s后的指针/引用语义
-
值语义意味着
-
调用
some_templated_function("123"s)
将简洁地确保<std::string>
实例化,参数能够在内部使用值语义处理- 如果你知道其他代码的实例化模板的
std::string
无论如何,它是显著的复杂性相对于你的资源限制,你可能想传递一个std::string
也避免不必要的const char*
的实例化,但很少需要关心
- 如果你知道其他代码的实例化模板的
-
包含嵌入式
NUL
s的值
c风格的字符串字面值可能更可取:
-
指针式语义是需要的(或者至少不是问题)
-
值只会传递给函数期望
const char*
无论如何,或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(); }
不是,std::string str(My_Enum e) { return e == Zero ? "0"s : "1"s; }
在始终使用动态分配(无SSO,或更长的文本)时过早悲观) -
你利用了编译时相邻c字串字面值的连接(例如
"abc" "xyz"
变成了一个连续的const char[]
字面值"abcxyz"
)——这在宏替换中特别有用 -
你是内存受限和/或不想在动态内存分配过程中出现异常或崩溃的风险
[basic.string。[字面量]21.7列表:
string operator "" s(const char* str, size_t len);
返回:
string{str,len}
基本上,使用""s
调用一个按值返回std::string
的函数——关键是,你可以绑定const
引用或右值引用,但不能绑定左值引用。
当用来调用void foo(std::string arg);
时,arg
将确实被移动构造。
同样,如果foo被重载以接受右值引用呢?在这种情况下,我认为调用foo("bar"s)是有意义的,但我可能错了。
你选择哪一个并不重要。维护方面——如果foo(const std::string&)
被更改为foo(const char*)
,只有foo("xyz");
调用将无缝地继续工作,但是有非常一些模糊合理的原因可能是(所以C代码也可以调用它?但是如果不继续为现有的客户端代码提供foo(const std::string&)
过载,那就有点疯狂了所以它可以在C语言中实现?——也许;移除对<string>
头的依赖?-与现代计算资源无关)。
std:: cout & lt; & lt;"Hello World !"& lt; & lt;std:: endl;//选项1
std:: cout & lt; & lt;你好,世界!"s <<std:: endl;//选项2
前者将调用operator<<(std::ostream&, const char*)
,直接访问常量字符串文字数据,唯一的缺点是流可能必须扫描终止NUL。选项2";将匹配const
引用重载,并暗示构建临时对象,尽管编译器可能能够优化它,因此它们不会经常不必要地这样做,甚至可以在编译时有效地创建字符串对象(这可能只适用于足够短的字符串,可以使用对象内短字符串优化(SSO)方法)。如果他们还没有做这样的优化,那么潜在的好处和这样做的压力/愿望可能会增加。
首先,我认为答案是基于意见的!
对于示例1,您已经提到了使用新的s
字面量的所有重要参数。是的,我希望结果是相同的,所以我认为没有必要在定义中说我想要一个std::string。
一个参数可以是,构造函数定义为explicit
,并且不会发生自动类型转换。在这种情况下,s
字面值是有用的。
但我认为这是一个品味问题!
对于你的例子2,我倾向于使用"旧的"c-string版本,因为生成std::string对象有开销。为cout提供指向字符串的指针是定义良好的,我认为没有任何用例可以从中受益。所以我的个人建议实际上是(每天都有新的信息可用:-))使用c-string,如果这完全符合我的需要。这意味着:字符串是常量,永远不会被复制或修改,只能按原样使用。所以std::string没有任何好处。
在需要定义std::string时使用's'-literal
简而言之:如果我不需要std::string比旧的c-string提供的额外功能,我就不会使用std::string。对我来说,关键不在于使用s字面值,而在于使用std::string与c-string的区别。
仅作为评论:我必须在非常小的嵌入式设备上编程,特别是在8位avr上。使用std::string会导致大量开销。如果我必须使用动态容器,因为我需要这个容器的特性,那么最好有一个实现和测试得很好的容器。但如果我不需要它,使用它就太贵了。
在像x86机器这样的大目标上,用std::string代替c-string似乎可以忽略不计。但是考虑到一个小设备,你就会知道在大机器上到底发生了什么。
只有我的两美分!在什么情况下,构造一个未命名的std::string对象而不是简单地使用c风格的字符串字面量是一个好(或坏)主意?
什么是好主意,什么不是好主意,往往随情况而变化。
我的选择是在足够的时候使用原始文字(当我不需要任何其他文字时)。如果我需要访问指向字符串第一个元素的指针以外的任何东西(字符串长度、返回值、迭代器或其他任何东西),那么我使用std::string literal。
当然,在所有情况下,构造std::string对象都可能导致堆分配,这可能引发异常,因此也应该考虑异常安全性。
喔…虽然代码确实可能抛出,但这无关紧要,除非在非常特殊的情况下(例如,运行在或接近硬件内存限制的嵌入式代码,或高可用性应用程序/环境)。
在实践中,我从来没有遇到过内存不足的情况,从编写auto a = "abdce"s;
或其他类似的代码。
总之,不需要为实例化std::string literal所产生的内存不足情况的异常安全性而烦恼。如果在执行此操作时遇到内存不足的情况,请在发现错误时更改代码。
- std::string 的对象真的可以移动吗?
- 在共享缓冲区内存中创建 ::std::string 对象
- 错误:无法传递非平凡类型"std::string"的对象和更多错误
- 将(临时的?)std::string传递给使用它来构造一个接受副本的对象的函数的最佳方法是什么?
- Swig无法将python3的字节对象转换为std::string
- 为什么用默认构造函数构造std::string对象时行为不同
- 如何从 boost::container::vector<std::string>::iterator 访问索引和对象?
- 如何改进我的"String"对象比较功能
- 对象的序列化向量为STD :: String,以与MPI一起使用
- 为什么<string>字符串对象需要 #include 而不是字符串文本?
- 将一个常量cstring附加到我自己的String类对象
- 为什么字符串的第一个输入没有分配给 std::string 对象?
- 模板化运算符 string() 在临时对象时无法编译
- 我可以在迭代 std::string 对象时删除它中的元素吗?
- 如何在不复制和保留 std::string 对象的情况下获得 C++ std::string 字符数据的所有权
- 为什么像cin,cout,string等东西被认为是对象
- 使用 std::ostream 方法将格式化文本输出到 std::string 对象
- 不能在没有对象的情况下调用成员函数 std::string class::function()
- C++将 HashMap<string、布尔>对象返回到 Java
- 无法传递非普通可复制类型“const class mysqlpp::String”的对象