Std :: stringの「+」演算子と、連結を高速化するためのさまざまな回避策についての懸念を表明する人が数人います。これらのどれかが本当に必要ですか?もしそうなら、C++で文字列を連結する最良の方法は何ですか?
実際に効率が必要な場合を除き、余分な作業はおそらく価値がありません。代わりに演算子+ =を使用するだけで、効率が大幅に向上します。
今、その免責事項の後、私はあなたの実際の質問に答えます...
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)
それぞれの+の後に新しいオブジェクトが返されることがわかります。これは、毎回新しいバッファーが使用されることを意味します。大量の余分な+操作を行う場合、効率的ではありません。
なぜもっと効率的にできるのか:
実装に関する考慮事項:
ロープのデータ構造:
本当に高速な連結が必要な場合は、 ロープデータ構造 の使用を検討してください。
前に最終スペースを確保してから、バッファでappendメソッドを使用します。たとえば、最終的な文字列の長さが100万文字になると予想するとします。
std::string s;
s.reserve(1000000);
while (whatever)
{
s.append(buf,len);
}
私はそれを心配しません。ループで実行すると、文字列は常にメモリを事前に割り当てて再割り当てを最小限に抑えます。その場合はoperator+=
を使用します。そして、あなたが手動でそれを行う場合、これ以上のようなもの
a + " : " + c
次に、一時的なものを作成します-コンパイラが戻り値のコピーを削除できたとしても。これは、連続して呼び出されるoperator+
では、参照パラメーターが名前付きオブジェクトを参照するか、sub operator+
呼び出しから返される一時的なものを参照するかがわからないためです。最初にプロファイルを作成する前に、心配する必要はありません。しかし、それを示すための例を取り上げましょう。最初に括弧を導入して、バインディングを明確にします。明確にするために使用される関数宣言の直後に引数を配置します。その下に、結果の式が何であるかを示します:
((a + " : ") + c)
calls string operator+(string const&, char const*)(a, " : ")
=> (tmp1 + c)
さて、その追加で、tmp1
は、示された引数でoperator +を最初に呼び出したときに返されたものです。コンパイラは本当に賢く、戻り値のコピーを最適化するものと想定しています。したがって、a
と" : "
の連結を含む1つの新しい文字列になります。今、これが起こります:
(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>
一時的なものと名前付きの文字列に対して同じ関数を使用しています!したがって、コンパイラhasは、引数を新しい文字列にコピーし、それに追加して、operator+
の本体から返します。一時のメモリを取得して追加することはできません。式が大きいほど、より多くの文字列のコピーを作成する必要があります。
次のVisual StudioとGCCは、c ++ 1xの移動セマンティクス(補完コピーセマンティクス)と実験的な追加としての右辺値参照をサポートします。これにより、パラメーターが一時を参照しているかどうかを判断できます。上記のすべてがコピーなしの1つの「追加パイプライン」になるため、これによりこのような追加が驚くほど高速になります。
それがボトルネックであることが判明した場合、あなたはまだ行うことができます
std::string(a).append(" : ").append(c) ...
append
呼び出しは、引数を*this
に追加し、それ自体への参照を返します。したがって、そこで一時的なコピーは行われません。または、operator+=
を使用できますが、優先順位を修正するにはfixい括弧が必要です。
ほとんどのアプリケーションでは、それは重要ではありません。コードを書くだけで、+演算子が正確にどのように機能するかをまったく気にせず、問題が明らかなボトルネックになった場合にのみ問題を自分の手に渡してください。
.NET System.Stringとは異なり、C++ std :: strings are mutableです。したがって、他のメソッドと同じ速さで単純な連結を介して構築できます。
代わりにおそらくstd :: stringstream?
しかし、私はあなたがおそらくそれを保守可能で理解しやすい状態に保ち、あなたが本当に問題を抱えているかどうかを確認するべきだという感情に同意します。
Imperfect C++で、Matthew Wilsonはdynamic文字列連結器を提示します。これは、すべての部分を連結する前に1つの割り当てのみを行うために最終文字列の長さを事前計算します。 expression templatesを使用して、静的な連結子を実装することもできます。
この種のアイデアは、STLport std :: string実装で実装されています。これは、この正確なハックのために標準に準拠していません。
std::string
operator+
は、新しい文字列を割り当てて、2つのオペランド文字列を毎回コピーします。何回も繰り返すと、高価になります、O(n)。
std::string
append
およびoperator+=
一方、文字列を大きくする必要があるたびに容量を50%増やします。これにより、メモリ割り当てとコピー操作の数が大幅に削減されます、O(log n)。
小さな文字列の場合、それは重要ではありません。大きな文字列がある場合は、ベクターまたはその他のコレクションとしてパーツとして保存することをお勧めします。そして、1つの大きな文字列ではなく、そのようなデータのセットで動作するようにアルゴリズムを追加します。
複雑な連結にはstd :: ostringstreamが好きです。
ほとんどの場合と同様に、何かをするよりもしないほうが簡単です。
大きな文字列を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!");
配列のサイズと割り当てられたバイト数を追跡するクラスにカプセル化された単純な文字の配列が最速です。
トリックは、開始時に1つの大きな割り当てを行うことです。
で
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 |