StringBuffer / StringBuilderに相当するC ++?


184

C#のStringBuilderまたはJavaのStringBufferと同様に、効率的な文字列連結機能を提供するC ++標準テンプレートライブラリクラスはありますか?


3
短い答えは次のとおりです。はい、STLにはそのためのクラスがあり、それはそうですstd::ostringstream
CoffeDeveloper 2015

@andrewさん、こんにちは。受け入れられた回答を変更していただけますか?明確な勝利の答えがあり、それは現在受け入れられている答えではありません。
null

回答:


53

この回答は最近注目を集めています。私はこれを解決策として主張していません(これは、STLの前に過去に見た解決策です)。これは興味深いアプローチであり、唯一の上に適用されるべきstd::stringか、std::stringstreamあなたのコードをプロファイリングした後、あなたが発見した場合、これは改善します。

通常、std::stringまたはを使用しますstd::stringstream。私はこれらに問題を経験したことがありません。事前に弦のおおよそのサイズを知っているのであれば、通常はまず部屋を予約します。

遠い昔、他の人々が独自の最適化された文字列ビルダーを作成するのを見てきました。

class StringBuilder {
private:
    std::string main;
    std::string scratch;

    const std::string::size_type ScratchSize = 1024;  // or some other arbitrary number

public:
    StringBuilder & append(const std::string & str) {
        scratch.append(str);
        if (scratch.size() > ScratchSize) {
            main.append(scratch);
            scratch.resize(0);
        }
        return *this;
    }

    const std::string & str() {
        if (scratch.size() > 0) {
            main.append(scratch);
            scratch.resize(0);
        }
        return main;
    }
};

2つの文字列を使用します。1つは文字列の大部分に使用し、もう1つは短い文字列を連結するためのスクラッチ領域として使用します。これは、短い追加操作を1つの小さな文字列にバッチ処理し、これをメイン文字列に追加することで、追加を最適化します。これにより、メイン文字列が大きくなるにつれて、必要な再割り当ての数が減ります。

std::stringまたはでこのトリックは必要ありませんstd::stringstream。std :: stringの前にサードパーティの文字列ライブラリで使用されていたと思います。それはずっと前のことです。このプロファイルのような戦略を採用する場合は、最初にアプリケーションを使用します。


13
車輪の再発明。std :: stringstreamが適切な答えです。以下の適切な回答を参照してください。
Kobor42 2013

13
@ Kobor42私は私の答えの最初と最後の行で指摘するようにあなたに同意します。
iain 2013

1
scratchここでは文字列が実際に何かを成し遂げるとは思わない。メイン文字列の再割り当ての数は、string実装が本当に貧弱でない限り(つまり、指数関数的増加を使用しない限り)、主に最終的なサイズの関数であり、追加操作の数ではありません。したがってappend、基盤stringを大きくすると、どちらか一方の方向にしか成長しないため、を「バッチ処理」しても効果がありません。その上、冗長なコピー操作の束を追加し、短い文字列に追加しているため、再割り当て(したがって/の呼び出し)が増える可能性があります。newdelete
BeeOnRope

@BeeOnRope同意します。
iain 2017年

私はstr.reserve(1024);これよりも速くなると確信しています
hanshenrik '25

160

C ++の方法は、std :: stringstreamまたは単純な文字列連結を使用することです。C ++文字列は可変であるため、連結のパフォーマンスに関する考慮事項はそれほど問題ではありません。

フォーマットに関しては、ストリーム上ですべて同じフォーマットを行うことができますが、方法はと似ていcoutます。または、これをカプセル化し、インターフェイスのようなString.Formatを提供する強く型付けされたファンクタを使用できます。例:boost :: format


59
C ++文字列は変更可能です。根本的な理由StringBuilder、Javaの不変の基本的なString型の非効率性カバーするためです。つまりStringBuilder、パッチワークなので、C ++でこのようなクラスが不要になったことをうれしく思います。
bobobobo 2013

57
@bobobobo不変文字列には他にも利点があります

8
プレーンな文字列連結は新しいオブジェクトを作成しないので、Javaの不変性と同じ問題ですか?次の例では、すべての変数が文字列であることを考慮してください。a= b + c + d + e + f; bとcでoperator +を呼び出し、次に結果でdとoperator +を呼び出すのではないですか?
Serge Rogatch

9
ちょっと待ってください。標準の文字列クラスはそれ自体を変更する方法を知っていますが、それは非効率がそこにないわけではありません。私が知る限り、std :: stringは内部のchar *のサイズを単純に拡張することはできません。つまり、より多くの文字を必要とする方法で変更するには、再割り当てとコピーが必要です。これは文字のベクトルと同じで、その場合に必要なスペースを予約する方が確かに優れています。
Trygve Skogsholm

7
@TrygveSkogsholm-文字のベクトルと同じですが、もちろん文字列の「容量」はそのサイズよりも大きくなる可能性があるため、すべての追加で再割り当てが必要になるわけではありません。一般的に、文字列は指数関数的成長戦略を使用するため、追加しても線形コスト操作に償却されます。これは、Javaの不変の文字列とは異なり、すべての追加操作で両方の文字列のすべての文字を新しい文字にコピーする必要があるため、一連の追加O(n)は一般的に終了します。
BeeOnRope

93

このstd::string.append関数は、多くの形式のデータを受け入れないため、適切なオプションではありません。より便利な代替手段はを使用することstd::stringstreamです。そのようです:

#include <sstream>
// ...

std::stringstream ss;

//put arbitrary formatted data into the stream
ss << 4.5 << ", " << 4 << " whatever";

//convert the stream buffer into a string
std::string str = ss.str();

43

std::string ある C ++と同等:それは可変です。


13

.append()を使用して、単に文字列を連結することができます。

std::string s = "string1";
s.append("string2");

あなたもできるかもしれないと思います:

std::string s = "string1";
s += "string2";

C#ののの書式設定の操作についてはStringBuilder、私は信じているsnprintf(あるいはsprintfあなたが;-)バグだらけのコードを書くリスクにしたい場合)は、文字列に文字列と変換背中に唯一のオプションについてです。


しかし、printfや.NETのString.Formatと同じではありませんか?
アンディシェラム

1
彼らが唯一の方法であると言うのは少し不誠実です
jk。

2
@jk-.NETのStringBuilderのフォーマット機能を比較する場合、これが唯一の方法です。これは、元の質問が具体的に尋ねたものです。私は「信じる」と言ったので間違っている可能性がありますが、printfを使用せずにC ++でStringBuilderの機能を取得する方法を教えてもらえますか?
アンディシェラム

私の回答を更新して、いくつかの代替の書式設定オプション
jk

6

std::stringC ++では可変なので、それを使用できます。それは持っている+= operatorappend機能を。

数値データを追加する必要がある場合は、std::to_string関数を使用してください。

オブジェクトを文字列にシリアル化できるという形でさらに柔軟性が必要な場合は、std::stringstreamクラスを使用します。ただし、独自のカスタムクラスを使用するには、独自のストリーミングオペレーター関数を実装する必要があります。


4

std :: stringの+ =はconst char *(「追加する文字列」のように見えるもの)では機能しないため、stringstreamを使用することは、必要なものに最も近いため、+の代わりに<<を使用するだけです。


3

C ++の便利な文字列ビルダー

以前に多くの人が答えたように、std :: stringstreamは選択する方法です。それはうまく機能し、多くの変換とフォーマットのオプションがあります。IMOには、非常に不便な欠点が1つあります。1つのライナーまたは式として使用することはできません。あなたはいつも書く必要があります:

std::stringstream ss;
ss << "my data " << 42;
std::string myString( ss.str() );

これは、特にコンストラクタで文字列を初期化したい場合には、かなり煩わしいものです。

その理由は、a)std :: stringstreamにはstd :: stringへの変換演算子がなく、b)stringstreamの演算子<<()はstringstream参照を返さず、代わりにstd :: ostream参照を返すためです。 -文字列ストリームとしてさらに計算することはできません。

解決策は、std :: stringstreamをオーバーライドして、より適切な演算子を与えることです。

namespace NsStringBuilder {
template<typename T> class basic_stringstream : public std::basic_stringstream<T>
{
public:
    basic_stringstream() {}

    operator const std::basic_string<T> () const                                { return std::basic_stringstream<T>::str();                     }
    basic_stringstream<T>& operator<<   (bool _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (char _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (signed char _val)                      { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned char _val)                    { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (short _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned short _val)                   { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (int _val)                              { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned int _val)                     { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long _val)                             { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned long _val)                    { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long long _val)                        { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (unsigned long long _val)               { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (float _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (double _val)                           { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (long double _val)                      { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (void* _val)                            { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::streambuf* _val)                  { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ostream& (*_val)(std::ostream&))  { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ios& (*_val)(std::ios&))          { std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (std::ios_base& (*_val)(std::ios_base&)){ std::basic_stringstream<T>::operator << (_val); return *this; }
    basic_stringstream<T>& operator<<   (const T* _val)                         { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val)); }
    basic_stringstream<T>& operator<<   (const std::basic_string<T>& _val)      { return static_cast<basic_stringstream<T>&>(std::operator << (*this,_val.c_str())); }
};

typedef basic_stringstream<char>        stringstream;
typedef basic_stringstream<wchar_t>     wstringstream;
}

これで、あなたは次のようなものを書くことができます

std::string myString( NsStringBuilder::stringstream() << "my data " << 42 )

コンストラクタでもです。

文字列の作成を多用する環境ではまだ使用していないので、パフォーマンスを測定しなかったことを告白する必要がありますが、すべてが完了しているため、std :: stringstreamよりも悪くはないと想定しています参照経由(文字列への変換を除くが、std :: stringstreamでのコピー操作でもある)


これはすごいです。std::stringstreamがこのように動作しない理由はわかりません。
アインポクルム

1

ロープの先文字列のランダムな場所に、または長い文字列のために/削除文字列を挿入する必要がある場合、コンテナは価値があるかもしれません。SGIの実装の例を次に示します。

crope r(1000000, 'x');          // crope is rope<char>. wrope is rope<wchar_t>
                                // Builds a rope containing a million 'x's.
                                // Takes much less than a MB, since the
                                // different pieces are shared.
crope r2 = r + "abc" + r;       // concatenation; takes on the order of 100s
                                // of machine instructions; fast
crope r3 = r2.substr(1000000, 3);       // yields "abc"; fast.
crope r4 = r2.substr(1000000, 1000000); // also fast.
reverse(r2.mutable_begin(), r2.mutable_end());
                                // correct, but slow; may take a
                                // minute or more.

0

次の理由から、何か新しいものを追加したいと思いました。

最初の試みで私は打つことに失敗しました

std::ostringstreamoperator<<

効率性は向上しましたが、場合によってはより高速なStringBuilderを作成することができました。

文字列を追加するたびに、どこかへの参照を格納し、合計サイズのカウンターを増やします。

私が最終的に実装した実際の方法(ホラー!)は、不透明なバッファー(std :: vector <char>)を使用することです。

  • 1バイトのヘッダー(次のデータが:移動した文字列、文字列、またはバイト[]かどうかを示す2ビット)
  • バイト長を示す6ビット[]

バイト[]

  • 短い文字列のバイトを直接格納します(順次メモリアクセス用)

移動された文字列(が追加された文字列std::move

  • std::stringオブジェクトへのポインター(所有権があります)
  • 未使用の予約済みバイトがある場合、クラスにフラグを設定します

文字列用

  • std::stringオブジェクトへのポインター(所有権なし)

小さな最適化も1つあります。最後に挿入された文字列がムーブインされた場合、不透明なバッファーを使用する代わりに、予約されているが未使用の空きバイトがチェックされ、そこにさらにバイトが格納されます(これはメモリを節約するためですが、実際には少し遅くなります、おそらくCPUにも依存しますが、とにかく余分な予約スペースを持つ文字列が表示されることはまれです)

これはついに少し速くなりましたstd::ostringstreamが、いくつかの欠点があります:

  • 固定長の文字型(つまり、1、2、または4バイト、UTF8には適さない)を想定しましたが、UTF8では機能しないと言っているわけではありません。ただ、遅延をチェックしていません。
  • 私は悪いコーディング方法を使用しました(不透明なバッファー、移植できないようにするのは簡単です、ところで私のものは移植可能だと思います)
  • のすべての機能がありません ostringstream
  • すべての文字列をマージする前に、参照されている文字列が削除された場合:未定義の動作。

結論?使用する std::ostringstream

それはすでに最大のボトルネックを修正していますが、鉱山の実装で数%の速度を達成することはマイナス面に値しません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.