web-dev-qa-db-ja.com

StringBuffer / StringBuilderのC ++相当?

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

150
An̲̳̳drew

注この回答は最近注目されています。私はこれを解決策として提唱していません(これは私が過去に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の前にサードパーティの文字列ライブラリで使用されていたと思います。このプロファイルのような戦略を最初に採用する場合、アプリケーション。

47
iain

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

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

133
jk.

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();
79
Stu

std::stringis C++に相当するもの:可変です。

38
dan04

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

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

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

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

C#のStringBuilderのフォーマット操作については、snprintf(またはバグのあるコードを書くリスクがある場合はsprintf)を文字配列に変換し、文字列に変換することが唯一のオプションだと思います。

9
Andy Shellam

C++のstd::stringは可変なので、それを使用できます。 += operatorおよびappend関数があります。

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

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

5
Daemin

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

3
sergeys

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の演算子<<() 'sは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のコピー操作でもあります)

2
user2328447

Rope コンテナは、宛先文字列のランダムな場所に文字列を挿入/削除する必要がある場合、または長い文字シーケンスの場合に価値があります。 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.
1
Igor

次の理由により、新しいものを追加したかったのです。

最初の試みで私は打ち負かさなかった

std::ostringstreamoperator<<

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

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

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

  • 1バイトのヘッダー(次のデータが:moved string、stringまたはbyte []であるかどうかを判断するための2ビット)
  • byte []の長さを伝える6ビット

バイト[]の場合

  • 短い文字列のバイトを直接保存します(シーケンシャルメモリアクセス用)

移動した文字列の場合std::moveが追加された文字列)

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

文字列用

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

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

これは最終的にstd::ostringstreamよりもわずかに高速でしたが、欠点はほとんどありません。

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

結論? std::ostringstreamを使用します

既に最大のボトルネックが修正されていますが、鉱山の実装で速度を数%上げることはマイナスの価値はありません。

0
GameDeveloper