我听到一些人在std :: string中表达了对“+”运算符的担忧以及加速连接的各种解决方法。这些都真的有必要吗?如果是这样,在C ++中连接字符串的最佳方法是什么?
答案 0 :(得分:79)
额外的工作可能不值得,除非你真的需要效率。你可能只需要使用operator + =来提高效率。
现在在免责声明之后,我会回答你的实际问题......
STL字符串类的效率取决于您正在使用的STL的实现。
您可以通过c内置函数手动连接来保证效率和拥有更强的控制力。
为什么operator +效率不高:
看一下这个界面:
template <class charT, class traits, class Alloc>
basic_string<charT, traits, Alloc>
operator+(const basic_string<charT, traits, Alloc>& s1,
const basic_string<charT, traits, Alloc>& s2)
您可以看到每个+后都返回一个新对象。这意味着每次都使用新的缓冲区。如果你正在做大量的额外+操作,那就没有效率了。
为什么你可以提高效率:
实施注意事项:
绳索数据结构:
如果您需要非常快速的连接,请考虑使用rope data structure。
答案 1 :(得分:70)
之前保留最后一个空格,然后使用带缓冲区的append方法。例如,假设您希望最终的字符串长度为100万个字符:
std::string s;
s.reserve(1000000);
while (whatever)
{
s.append(buf,len);
}
答案 2 :(得分:16)
我不担心。如果你在循环中执行它,字符串将始终预分配内存以最小化重新分配 - 在这种情况下只使用operator+=
。如果你手动完成,可以这样或更长时间
a + " : " + c
然后它正在创造临时性 - 即使编译器可以消除一些返回值副本。这是因为在连续调用operator+
时,它不知道引用参数是引用命名对象还是从子operator+
调用返回的临时对象。在没有首先进行分析之前,我宁愿不担心它。但让我们举一个例子来证明这一点。我们首先引入括号以使绑定清晰。我将参数直接放在用于清晰的函数声明之后。在下面,我展示了结果表达式:
((a + " : ") + c)
calls string operator+(string const&, char const*)(a, " : ")
=> (tmp1 + c)
现在,在该添加中,tmp1
是第一次调用operator +并返回显示的参数。我们假设编译器非常聪明并优化了返回值副本。因此,我们最终得到一个包含a
和" : "
串联的新字符串。现在,这发生了:
(tmp1 + c)
calls string operator+(string const&, string const&)(tmp1, c)
=> tmp2 == <end result>
将其与以下内容进行比较:
std::string f = "hello";
(f + c)
calls string operator+(string const&, string const&)(f, c)
=> tmp1 == <end result>
它对临时和命名字符串使用相同的函数!所以编译器有将参数复制到一个新的字符串中并附加到该字符串并从operator+
的主体返回。它不能记住一个临时的并追加它。表达式越大,字符串的副本就越多。
接下来,Visual Studio和GCC将支持c ++ 1x的移动语义(补充复制语义)和rvalue引用作为实验性添加。这允许确定参数是否引用临时参数。这将使得这样的添加速度惊人地快,因为上述所有内容将最终出现在一个没有副本的“添加管道”中。
如果结果是瓶颈,你仍然可以
std::string(a).append(" : ").append(c) ...
append
次调用将参数追加到*this
,然后返回对自己的引用。因此,那里没有复制临时工。或者,可以使用operator+=
,但是您需要使用丑陋的括号来修复优先级。
答案 3 :(得分:11)
对于大多数应用程序来说,这无关紧要。只需编写代码,幸福地不知道+运算符的工作原理,只有当它成为一个明显的瓶颈时,才能自己动手。
答案 4 :(得分:7)
与.NET System.Strings不同,C ++的std :: strings 是可变的,因此可以通过简单的连接来构建,就像通过其他方法一样快。
答案 5 :(得分:5)
或许是std :: stringstream?
但我同意这样的观点,即你应该保持它的可维护性和可理解性,然后分析一下,看看你是否确实遇到了问题。
答案 6 :(得分:4)
在 Imperfect C ++ 中,Matthew Wilson提供了一个动态字符串连接器,它预先计算最终字符串的长度,以便在连接所有部分之前只进行一次分配。我们还可以通过使用表达式模板来实现静态连接器。
这种想法已在STLport std :: string实现中实现 - 由于这种精确的黑客攻击而不符合标准。
答案 7 :(得分:3)
std::string
operator+
每次分配一个新字符串并复制两个操作数字符串。重复多次,它变得昂贵,O(n)。
std::string
append
和operator+=
会在每次字符串需要增长时将容量提高50%。这显着减少了内存分配和复制操作的数量,O(log n)。
答案 8 :(得分:2)
对于小字符串,无所谓。 如果你有大字符串,你最好将它们存储为矢量或其他集合中的部分。并添加您的算法以使用这样的数据集而不是一个大字符串。
我更喜欢std :: ostringstream用于复杂的连接。
答案 9 :(得分:2)
与大多数事情一样,做某事比做事更容易。
如果你想将大字符串输出到GUI,可能是你输出的任何内容都可以比一个大字符串更好地处理字符串(例如,在文本编辑器中连接文本 - 通常它们保持不变线作为单独的结构)。
如果要输出到文件,请流式传输数据,而不是创建大字符串并输出。
如果我从慢速代码中删除不必要的连接,我从未发现需要更快地进行连接。
答案 10 :(得分:0)
一个简单的字符数组,封装在一个跟踪数组大小和分配字节数的类中是最快的。
诀窍是在开始时只做一个大的分配。
在
https://github.com/pedro-vicente/table-string
对于Visual Studio 2015,x86调试构建,对C ++ std :: string的改进。
| API | Seconds
| ----------------------|----|
| SDS | 19 |
| std::string | 11 |
| std::string (reserve) | 9 |
| table_str_t | 1 |
答案 11 :(得分:0)
如果在结果字符串中预分配(保留)空间,则可能是最佳性能。
template<typename... Args>
std::string concat(Args const&... args)
{
size_t len = 0;
for (auto s : {args...}) len += strlen(s);
std::string result;
result.reserve(len); // <--- preallocate result
for (auto s : {args...}) result += s;
return result;
}
用法:
std::string merged = concat("This ", "is ", "a ", "test!");
答案 12 :(得分:0)
您可以尝试使用每个项目的内存保留功能来进行此操作:
namespace {
template<class C>
constexpr auto size(const C& c) -> decltype(c.size()) {
return static_cast<std::size_t>(c.size());
}
constexpr std::size_t size(const char* string) {
std::size_t size = 0;
while (*(string + size) != '\0') {
++size;
}
return size;
}
template<class T, std::size_t N>
constexpr std::size_t size(const T (&)[N]) noexcept {
return N;
}
}
template<typename... Args>
std::string concatStrings(Args&&... args) {
auto s = (size(args) + ...);
std::string result;
result.reserve(s);
return (result.append(std::forward<Args>(args)), ...);
}