我听到有一些人对std :: string中的“+”运算符表示担忧,并提出各种加速字符串连接的解决方案。这些方法是否真的必要?如果是,什么是在C ++ 中拼接字符串的最佳方式?
我听到有一些人对std :: string中的“+”运算符表示担忧,并提出各种加速字符串连接的解决方案。这些方法是否真的必要?如果是,什么是在C ++ 中拼接字符串的最佳方式?
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)
如果您需要非常快速的字符串连接,请考虑使用绳子数据结构。
在最后预留空间,然后使用带有缓冲区的append方法。例如,假设您期望最终字符串长度为100万个字符:
std::string s;
s.reserve(1000000);
while (whatever)
{
s.append(buf,len);
}
我不会担心这个问题。如果您在循环中执行此操作,则字符串始终会预先分配内存以最小化重新分配 - 只需在这种情况下使用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的移动语义(补充复制语义)和右值引用作为实验性添加。这可以确定参数是否引用临时对象。这将使得这种添加非常快速,因为所有上述操作最终都会在一个“添加管道”中完成而不需要额外复制。
如果发现存在瓶颈,仍然可以执行以下操作
std::string(a).append(" : ").append(c) ...
append
调用会将其参数附加到 *this
并返回对自身的引用。因此,在这里不会复制临时对象。或者,可以使用 operator+=
,但需要使用丑陋的括号来修复优先级。
libstdc++
对于operator+(string const& lhs, string&& rhs)
执行return std::move(rhs.insert(0, lhs))
。然后,如果两个都是临时对象,它的operator+(string&& lhs, string&& rhs)
如果lhs
有足够的可用容量,将直接进行append()
。我认为这比operator+=
慢的风险在于,如果lhs
没有足够的容量,则会回退到rhs.insert(0, lhs)
,这不仅必须像append()
一样扩展缓冲区并添加新内容,还需要向右移动rhs
的原始内容。 - underscore_doperator+=
相比,另一个开销是 operator+
仍然必须返回一个值,因此它必须 move()
添加到的任何操作数。不过,我想相比于深度复制整个字符串,这只是一个相当小的开销(复制一些指针/大小),所以很好! - underscore_dstd::string
的 operator+
每次都会分配一个新的字符串并复制两个操作数字符串。如果重复多次,它会变得很昂贵,时间复杂度为O(n)。
另一方面,std::string
的 append
和 operator+=
在字符串需要增长时每次都会将容量增加50%。这将大大降低内存分配和复制操作的数量,时间复杂度为O(log n)。
operator+
的重载可以避免分配新的字符串,而是通过将其连接到一个操作数的现有缓冲区来传递一个或两个参数的右值引用(尽管如果容量不足,可能需要重新分配)。 - underscore_d对于大多数应用程序而言,这并不重要。只需编写代码,无需关注+运算符的确切工作方式,只有在它成为明显的瓶颈时才需要自行处理。
operator+
不一定要返回一个新的字符串。如果实现者通过右值引用传递了其中一个操作数,那么可以返回其中一个操作数进行修改。例如,libstdc++
就是这样做的(参见 https://dev59.com/9nRB5IYBdhLWcg3weHLx#5t7onYgBc1ULPQZF5Fed)。因此,当使用临时对象调用 operator+
时,它可以达到与返回新字符串相同或几乎相同的性能 - 这可能是默认使用它的另一个支持理由,除非有基准测试表明它代表了瓶颈。 - underscore_d对于小字符串,这并不重要。如果你有大字符串,最好将它们作为向量或其他集合的部分存储起来。并且调整你的算法以使用这样的数据集而不是一个大字符串。
我更喜欢使用std::ostringstream进行复杂的连接操作。
和大多数事情一样,不做某件事比做它更容易。
如果您想将大字符串输出到GUI,则可能无论您输出到什么都可以将字符串处理为较小的片段,而不是作为一个大字符串(例如,在文本编辑器中连接文本 - 通常它们将行保留为单独的结构)。
如果您想要输出到文件,请流式传输数据,而不是创建大字符串并将其输出。
我从未发现需要加快连接速度,如果我从慢速代码中删除了不必要的连接。
libstdc++
中的实现方式。因此,当使用临时对象调用 operator+ 时,它几乎可以达到与其他方式相同的性能 - 这可能是默认情况下选择它的论点,出于可读性的考虑,除非有基准测试表明它是瓶颈。但是,一个标准化的可变参数append()
不仅最优,而且更加易读... - underscore_d