web-dev-qa-db-ja.com

C ++ std :: string appendとPush_back()

これは本当に、ドキュメントだけでは判断できなかった自分自身の関心のための質問です。

http://www.cplusplus.com/reference/string/string/ を見ると、appendは複雑です。

「未指定ですが、通常、新しい文字列の長さは線形までです。」

push_back()は複雑ですが、

「未指定。通常は償却後の定数ですが、新しい文字列の長さでは線形までです。」

おもちゃの例として、文字列に「foo」という文字を追加したいとします。だろう

myString.Push_back('f');
myString.Push_back('o');
myString.Push_back('o');

そして

myString.append("foo");

まったく同じものですか?それとも何か違いはありますか? Push_backが各呼び出しでメモリを確保する必要があるのに対して、コンパイラは文字列を指定された文字数拡張するために必要なメモリ量を知っているため、appendの方が効率的であると考えているかもしれません。

24
Memento Mori

C++ 03(「cplusplus.com」のドキュメントのほとんどが記述されている)では、ライブラリの実装者が文字列のコピーオンライトまたは「ロープスタイル」の内部表現を実行できるため、複雑さは規定されていませんでした。たとえば、COWの実装では、文字が変更され、共有が行われている場合、文字列全体をコピーする必要があります。

C++ 11では、COWとロープの実装は禁止されています。最後に文字列に追加するために、追加された文字ごとの一定の償却時間または追加された文字数の線形の償却時間を期待する必要があります。実装者は(たとえばstd::vectorと比較して)文字列を使って比較的クレイジーなことをするかもしれませんが、ほとんどの実装は「小さな文字列の最適化」のようなものに限定されます。

Push_backappendを比較すると、Push_backは、潜在的に有用な長さ情報の基礎となる実装を奪い、スペースを事前に割り当てるために使用する可能性があります。一方、appendは、その長さを見つけるために実装が入力を2回ウォークする必要があるため、パフォーマンスの向上または低下は、文字列の長さなどの多くの認識できない要因に依存します。追加を試みる前に。とはいえ、その差はおそらく非常に非常に小さいものです。これにはappendを使用してください。はるかに読みやすくなっています。

37
Billy ONeal

私も同じ疑問を持っていたので、これを確認するために小さなテストを行いました(VmWare FusionでLinux、Intel、64ビットのC++ 11プロファイルを使用したg ++ 4.8.5)。

そして結果は興味深いです:

プッシュ:19 
追加:21 
 ++++:34 

これは文字列の長さ(大きな)が原因である可能性がありますが、演算子+はPush_backや追加に比べて非常に高価です。

また、演算子が文字列ではなく文字のみを受け取る場合、Push_backと非常によく似た動作をすることも興味深い点です。

事前に割り当てられた変数に依存しないように、各サイクルは異なるスコープで定義されます。

注:vCounterは単にgettimeofdayを使用して違いを比較します。

TimeCounter vCounter;

{
    string vTest;

    vCounter.start();
    for (int vIdx=0;vIdx<1000000;vIdx++) {
        vTest.Push_back('a');
        vTest.Push_back('b');
        vTest.Push_back('c');
    }
    vCounter.stop();
    cout << "Push :" << vCounter.elapsed() << endl;
}

{
    string vTest;

    vCounter.start();
    for (int vIdx=0;vIdx<1000000;vIdx++) {
        vTest.append("abc");
    }
    vCounter.stop();
    cout << "append :" << vCounter.elapsed() << endl;
}

{
    string vTest;

    vCounter.start();
    for (int vIdx=0;vIdx<1000000;vIdx++) {
        vTest += 'a';
        vTest += 'b';
        vTest += 'c';
    }
    vCounter.stop();
    cout << "++++ :" << vCounter.elapsed() << endl;
}
4
user2583872

ここにもう1つ意見を追加してください。

個人的には、別の文字列から文字を1つずつ追加する場合はPush_back()を使用する方がよいと考えています。例えば:

_string FilterAlpha(const string& s) {
  string new_s;
  for (auto& it: s) {
    if (isalpha(it)) new_s.Push_back(it);
  }
  return new_s;
}
_

append() hereを使用する場合、Push_back(it)append(1,it)に置き換えます。

3
Pei Z

はい、私はappend()が、あなたが与えた理由、およびappend()(または_operator+=_)を使用して文字列を追加する必要がある状況でより良いパフォーマンスを期待します確かに望ましいです(特に、コードがはるかに読みやすくなるためです)。

しかし、規格が指定しているのは、操作の複雑さです。そして、最終的に追加される文字列の各文字(および再割り当てが発生した場合はすべての文字)をコピーする必要があるため、これはappend()の場合でも一般的に線形です(これはmemcpyまたは同様のものが使用されます)。

0
jogojapan