我听到一些人对std :: string中的“ +”运算符表示担忧,并提出了各种解决方法以加快连接速度。这些真的有必要吗?如果是这样,在C ++中串联字符串的最佳方法是什么?
libstdc++
例如这样做。因此,当使用临时对象调用operator +时,它可以实现几乎一样好的性能-出于可读性考虑,也许赞成默认使用该参数,除非有基准表明它是瓶颈。但是,标准化的可变参数append()
将是最佳且可读的...
我听到一些人对std :: string中的“ +”运算符表示担忧,并提出了各种解决方法以加快连接速度。这些真的有必要吗?如果是这样,在C ++中串联字符串的最佳方法是什么?
libstdc++
例如这样做。因此,当使用临时对象调用operator +时,它可以实现几乎一样好的性能-出于可读性考虑,也许赞成默认使用该参数,除非有基准表明它是瓶颈。但是,标准化的可变参数append()
将是最佳且可读的...
Answers:
除非您确实需要效率,否则额外的工作可能不值得。 仅使用运算符+ =可能会带来更高的效率。
现在,在免责声明之后,我将回答您的实际问题...
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)
您可以看到每个+之后都会返回一个新对象。这意味着每次都使用一个新的缓冲区。如果您要执行大量额外的+操作,则效率不高。
为什么可以提高效率:
实施注意事项:
绳索数据结构:
如果确实需要快速串联,请考虑使用绳索数据结构。
我不会担心。如果循环执行,字符串将始终预分配内存以最大程度地减少重新分配-仅operator+=
在这种情况下使用。如果您手动执行此操作,则可能需要更长的时间
a + " : " + c
然后它会创建临时文件-即使编译器可以消除一些返回值副本。这是因为在连续调用中,operator+
它不知道引用参数是引用命名对象还是子operator+
调用返回的临时对象。我宁愿不用先介绍一下就不必担心。但是,让我们举一个例子来说明这一点。我们首先引入括号以使绑定清楚。我将参数直接放在用于清晰起见的函数声明之后。在此之下,我显示了结果表达式是什么:
((a + " : ") + c)
calls string operator+(string const&, char const*)(a, " : ")
=> (tmp1 + c)
现在,此外,tmp1
还有第一次调用operator +显示的参数返回的内容。我们假设编译器确实很聪明,并且优化了返回值副本。因此,我们最终得到了一个新字符串,其中包含a
and 的串联" : "
。现在,这发生了:
(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
权利的原始内容转移。
operator+=
是operator+
仍必须返回一个值,因此它必须move()
附加到它所附加的任何操作数上。不过,与深度复制整个字符串相比,我想这还是一个相当小的开销(复制几个指针/大小),所以很好!
对于大多数应用程序,这无关紧要。只需编写您的代码,就很高兴地不知道+运算符的工作原理,并且只有当它成为明显的瓶颈时,才由您自己处理。
与.NET System.Strings不同,C ++的std :: strings 是可变的,因此可以通过简单的串联来构建,就像通过其他方法一样快。
operator+
不必返回新字符串。如果该操作数是通过右值引用传递的,则实现者可以返回其操作数之一(已修改)。libstdc++
例如这样做。因此,在operator+
使用临时对象进行调用时,它可以实现相同或几乎相同的性能-这可能是赞成默认设置的另一种说法,除非有人有基准表明它代表瓶颈。
也许是std :: stringstream代替?
但是我同意这样一种观点,即您应该仅使其保持可维护性和可理解性,然后进行概要分析以查看您是否确实遇到问题。
在Imperfect C ++中,马修·威尔逊(Matthew Wilson)提出了一种动态字符串连接器,该功能可预先计算最终字符串的长度,以便在连接所有部分之前仅分配一次。我们还可以通过使用表达式模板来实现静态连接器。
这种想法已经在STLport std :: string实现中实现-由于这种精确的破解,因此不符合标准。
Glib::ustring::compose()
从glibmm绑定到GLib的操作是:reserve()
根据提供的格式字符串和varargs 估计和s最终长度,然后将append()
每个(或其格式化替换)循环。我希望这是一种非常普遍的工作方式。
std::string
operator+
分配一个新字符串,并每次都复制两个操作数字符串。重复很多次,它会变得很昂贵,O(n)。
std::string
append
而operator+=
在另一方面,50%每次字符串需要成长时间撞击的能力。这显着减少了内存分配和复制操作的数量O(log n)。
operator+
右值引用传递一个或两个参数的位置的重载可以避免通过串联到现有的缓冲区中来分配新字符串。操作数之一(如果容量不足,则可能必须重新分配)。
与大多数事情一样,不做某事比做某事更容易。
如果要将大字符串输出到GUI,则可能是要输出的任何内容都比大字符串更好地处理字符串(例如,在文本编辑器中连接文本-通常它们将行分开结构)。
如果要输出到文件,请流式传输数据,而不要创建一个大字符串并输出。
如果我从慢速代码中删除了不必要的连接,我从未发现有必要使连接更快变得必要。
如果在结果字符串中预分配(保留)空间,则可能是最佳性能。
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!");
封装在一个类中的简单字符数组是最快的,该类跟踪数组的大小和分配的字节数。
诀窍是在开始时只进行一次大分配。
在
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 |
std::string
。他们不要求替代字符串类。
您可以尝试对每个项目进行内存保留的方法:
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)), ...);
}