【C++11】字符串拼接之回归原点


在其他语言里,字符串拼接可能是一个常见而且基本不会去注意的部分,但是在C++中字符串拼接有非常多的解决方法。造成这种现象的原因是,C++程序员想要高效地拼接字符串。

比如说下面的代码

对于有非C/C++语言的人来说可能最平常不过的代码,C++程序员可能直觉上不会采用这种写法。那么C++里面该用什么写法呢?或者说最佳实践是什么?

这里不会列举各种字符串拼接的方式,如果你有兴趣可以在StackOverflow上搜搜看。个人想要说的是:在分析了C++11里字符串的操作之后个人给出的结论:C++11里最佳的字符串拼接其实就是上述写法。以下是具体分析。

首先给出一个模拟std::string的MyString类。

MyString类支持copy/move,以及重载了+=操作符。

在MyString的方法实现里面,加了部分debug代码,可以让你理解字符串拼接时实际哪些方法被调用了。

对其他语言背景的人来说,需要知道std::string其实比实际内容多留了一些空间方便追加字符,所以std::string是可变而且空间利用率不是100%。

这里MyString并没有像std::string一样留一些空间,不过这不影响分析。

调用代码如下

如果你想在尝试编译的话,肯定是无法通过的,因为操作符+没有被重载。以下是最基本的操作符重载。

方法前带有friend,所以请写在MyString类里面。

可以看到上述代码,都有一个result的变量。因为输入参数都是const,无法修改。

这里假如你不给参数加const,用临时变量调用的代码无法编译通过,如果干脆const和引用&都不加的话,第二个用栈上变量调用的代码会复制name的内容,这可能不是你想要的。所以方法签名是const T&,代码中也必须执行一次到result复制。

这时编译并执行后得到的第一个调用的输出

可以看到copy了两次。理论上这是正确的,因为你+了两次,产生了两个临时result变量。

个人认为,因为上述原因,很多C++程序员可能不会选择开篇的那种写法。从效率上来说,最理想的状态是,只开辟一个result变量,所有字符串都往result拼接。所以产生了如下的几种写法

老实说这两种写法没有太大区别。虽然效率可能比较高,但是写法不直观。那么有没有其他的方法呢?

如果你仔细观察C++11引入的新的std::string对应操作符+重载的方法签名的话,你可能会发现几个带有T&&的方法签名

这些方法签名会带来哪些变化呢?第一个就应该是lhs可以不是const了,也就是说lhs可以修改了!以下是模拟代码(注意返回时必须使用std::move,否则会变成复制。如果有怀疑的话可以看std::string中的源码)

可以看到result变量消失了,所有方法都在修改lhs。那么这是否就意味着传入的参数会直接被修改呢?比如调用代码中的第二种方式。

答案是不会,因为在concat_string方法里name是const String&,所以匹配的是旧方法,而不是新加的这几个方法。

实际执行调用代码(第一种和第二种结果一样)

可以很清晰地看到,新方法在第二个+的时候被调用了。又由于新方法中不会复制,所以是一次copy加一次move。

对比一下,不通过concat_string而是直接调用的方式

临时变量的话,两次move。

一次copy,一次move。

小结一下,在C++11中,字符串的拼接支持了move,减少了copy的次数。

如果你要问是否可以不copy?那么请考虑下,C++11之前没有copy的那两种写法,即“创建一个result中间变量加上第一次字符串拼接”与“用第一个字符串复制构造一个中间变量”其实本质上没有区别,重要的是中间没有重复创建result变量就行。

作为参考,其他几种可能的写法

效率上没有太大区别,但是相信你也会认为第一种是最好的。

最后,C++11增加的字符串拼接中,包含了以下两个方法

也就是说,你可以这么写代码

而不用显示的去用const char*去构造一个std::string。

总结

字符串拼接是开发中常见的代码,所以这种基础代码中在C++11中能按照直觉写而不用担心效率着实是一件很好的事情,有一种写了那么长时间便扭的C++代码回到了原点的感觉。最后希望我的分析对各位有用。


One response to “【C++11】字符串拼接之回归原点”

  1. 感谢分享!已推荐到《开发者头条》:https://toutiao.io/posts/q6l8cu 欢迎点赞支持!使用开发者头条 App 搜索 385148 即可订阅《并发与分布式系统研究》