C# のStringBuilderまたは Java のStringBufferに似た、効率的な文字列連結機能を提供する C++ 標準テンプレート ライブラリ クラスはありますか?
10 に答える
C++ の方法は、std::stringstreamまたは単純な文字列連結を使用することです。C++ 文字列は変更可能であるため、連結のパフォーマンスに関する考慮事項はそれほど重要ではありません。
フォーマットに関しては、ストリームに対してすべて同じフォーマットを行うことができますが、cout
. または、これをカプセル化し、 Boost::formatなどの String.Format のようなインターフェイスを提供する、厳密に型指定されたファンクターを使用できます。
この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();
この回答は最近注目を集めていることに注意してください。私はこれを解決策として提唱しているわけではありません (これは、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
orでこのトリックを必要としませんでしたstd::stringstream
。std::string の前にサードパーティの文字列ライブラリで使用されていたと思いますが、それはずっと前のことです。このプロファイルのような戦略を採用する場合は、最初にアプリケーションを作成します。
std::string
はC++ に相当します。変更可能です。
単純に文字列を連結するには .append() を使用できます。
std::string s = "string1";
s.append("string2");
あなたもできるかもしれないと思います:
std::string s = "string1";
s += "string2";
C# のフォーマット操作に関しては、StringBuilder
(snprintf
またはsprintf
、バグのあるコードを書く危険を冒したい場合は ;-)) 文字配列に変換し、文字列に戻すことが唯一のオプションだと思います。
C++ ではミュータブルなのでstd::string
、それを使用できます。と 機能が+= operator
ありappend
ます。
数値データを追加する必要がある場合は、std::to_string
関数を使用します。
任意のオブジェクトを文字列にシリアル化できるという形でさらに柔軟性が必要な場合は、std::stringstream
クラスを使用します。ただし、独自のカスタム クラスを操作するには、独自のストリーミング オペレーター関数を実装する必要があります。
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::string の += は const char* (「追加する文字列」のようなもの) では機能しないため、間違いなく stringstream を使用することが必要なものに最も近いです - + の代わりに << を使用するだけです
次の理由から、何か新しいものを追加したかったのです。
最初の試みで私は打ち負かすことができませんでした
std::ostringstream
のoperator<<
効率的ですが、より多くの試みで、場合によってはより高速な StringBuilder を作成できました。
文字列を追加するたびに、それへの参照をどこかに保存し、合計サイズのカウンターを増やします。
私が最終的に実装した実際の方法 (ホラー!) は、不透明なバッファー (std::vector < char > ) を使用することです。
- 1 バイトのヘッダー (次のデータが移動文字列、文字列、またはバイト [] のいずれであるかを示す 2 ビット)
- byte[] の長さを示す 6 ビット
バイト [ ] の場合
- 短い文字列のバイトを直接保存します(順次メモリアクセス用)
移動された文字列( が追加された文字列std::move
)の場合
std::string
オブジェクトへのポインター(私たちは所有権を持っています)- 未使用の予約済みバイトがある場合は、クラスにフラグを設定します
文字列用
std::string
オブジェクトへのポインター(所有権なし)
小さな最適化も 1 つあります。最後に挿入された文字列が移動された場合、予約済みで未使用の空きバイトをチェックし、不透明なバッファを使用する代わりにそこにさらにバイトを格納します (これはメモリを節約するためであり、実際には少し遅くなります)。 、CPUにも依存する可能性があり、とにかく余分な予約スペースを持つ文字列を見ることはめったにありません)
これは最終的に よりわずかに高速になりstd::ostringstream
ましたが、欠点はほとんどありません。
- 固定長の char 型 (つまり、1、2、または 4 バイト、UTF8 には適していません) を想定していましたが、UTF8 では機能しないと言っているわけではありません。怠惰をチェックしていません。
- 私は悪いコーディング慣行を使用しました(不透明なバッファ、簡単に移植できません、ちなみに私のものは移植可能だと思います)
- のすべての機能を欠いている
ostringstream
- すべての文字列をマージする前に、参照された文字列が削除された場合: 未定義の動作。
結論?使用する
std::ostringstream
最大のボトルネックはすでに修正されていますが、地雷の実装で速度が数%向上することは、マイナス面に値するものではありません。