C#のStringBuilderまたはJavaのStringBufferと同様に、効率的な文字列連結機能を提供するC ++標準テンプレートライブラリクラスはありますか?
C#のStringBuilderまたはJavaのStringBufferと同様に、効率的な文字列連結機能を提供するC ++標準テンプレートライブラリクラスはありますか?
回答:
この回答は最近注目を集めています。私はこれを解決策として主張していません(これは、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の前にサードパーティの文字列ライブラリで使用されていたと思います。それはずっと前のことです。このプロファイルのような戦略を採用する場合は、最初にアプリケーションを使用します。
scratch
ここでは文字列が実際に何かを成し遂げるとは思わない。メイン文字列の再割り当ての数は、string
実装が本当に貧弱でない限り(つまり、指数関数的増加を使用しない限り)、主に最終的なサイズの関数であり、追加操作の数ではありません。したがってappend
、基盤string
を大きくすると、どちらか一方の方向にしか成長しないため、を「バッチ処理」しても効果がありません。その上、冗長なコピー操作の束を追加し、短い文字列に追加しているため、再割り当て(したがって/の呼び出し)が増える可能性があります。new
delete
str.reserve(1024);
これよりも速くなると確信しています
C ++の方法は、std :: stringstreamまたは単純な文字列連結を使用することです。C ++文字列は可変であるため、連結のパフォーマンスに関する考慮事項はそれほど問題ではありません。
フォーマットに関しては、ストリーム上ですべて同じフォーマットを行うことができますが、方法はと似ていcout
ます。または、これをカプセル化し、インターフェイスのようなString.Formatを提供する強く型付けされたファンクタを使用できます。例:boost :: format
StringBuilder
は、Javaの不変の基本的なString型の非効率性をカバーするためです。つまりStringBuilder
、パッチワークなので、C ++でこのようなクラスが不要になったことをうれしく思います。
O(n)
は一般的に終了します。
この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();
.append()を使用して、単に文字列を連結することができます。
std::string s = "string1";
s.append("string2");
あなたもできるかもしれないと思います:
std::string s = "string1";
s += "string2";
C#ののの書式設定の操作についてはStringBuilder
、私は信じているsnprintf
(あるいはsprintf
あなたが;-)バグだらけのコードを書くリスクにしたい場合)は、文字列に文字列と変換背中に唯一のオプションについてです。
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
がこのように動作しない理由はわかりません。
ロープの先文字列のランダムな場所に、または長い文字列のために/削除文字列を挿入する必要がある場合、コンテナは価値があるかもしれません。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.
次の理由から、何か新しいものを追加したいと思いました。
最初の試みで私は打つことに失敗しました
std::ostringstream
の operator<<
効率性は向上しましたが、場合によってはより高速なStringBuilderを作成することができました。
文字列を追加するたびに、どこかへの参照を格納し、合計サイズのカウンターを増やします。
私が最終的に実装した実際の方法(ホラー!)は、不透明なバッファー(std :: vector <char>)を使用することです。
バイト[]
移動された文字列(が追加された文字列std::move
)
std::string
オブジェクトへのポインター(所有権があります)文字列用
std::string
オブジェクトへのポインター(所有権なし)小さな最適化も1つあります。最後に挿入された文字列がムーブインされた場合、不透明なバッファーを使用する代わりに、予約されているが未使用の空きバイトがチェックされ、そこにさらにバイトが格納されます(これはメモリを節約するためですが、実際には少し遅くなります、おそらくCPUにも依存しますが、とにかく余分な予約スペースを持つ文字列が表示されることはまれです)
これはついに少し速くなりましたstd::ostringstream
が、いくつかの欠点があります:
ostringstream
結論?使用する
std::ostringstream
それはすでに最大のボトルネックを修正していますが、鉱山の実装で数%の速度を達成することはマイナス面に値しません。
std::ostringstream
。