2011-12-05 15 views
13

私のC++プロジェクトでは、すべてchar*std::stringに置き換える前に一歩前ですが、std::stringが惨めに失敗することがあります。C++の文字列が文字列リテラルのために十分に最適化されていない

は、私はこれらの2つの機能を持っている想像:私はこのような何か書く場合

void foo1(const std::string& s) 
{ 
    ... 
} 

void foo2(const char* s) 
{ 
    ... 
} 

は:

const char* SL = "Hello to all!"; 

foo1(SL); // calls malloc, memcpy, free 
foo2(SL); 

foo1SLは、暗黙的にstd::stringに変換されます。これは、std::stringコンストラクタがメモリを割り当て、文字列リテラルをそのバッファにコピーすることを意味します。 foo2では、これらのすべてが起こることはありません。

ほとんどの実装では、std::stringは超最適化されています(たとえばCopy On Write)が、const char*で構成するとそうではありません。そして私の質問はこれです:なぜこれが起こるのですか?何か不足していますか?私の標準ライブラリは十分に最適化されていないか、何らかの理由で(これは私が気づいていない)完全に安全ではありませんか?

const char *a = "Hello World"; 
const char *b = new char[20]; 

のchar *はポインタはいつでも無効受け取ることがあります。

+4

コピーオンライトは実際には「スーパー最適化」ではありません。私は、GCCの標準ライブラリはまだそれを使用していると信じていますが、10年前にマルチスレッドが標準となる前にそれが理にかなっていたからです。今日作られた純粋なライブラリの実装は、疫病のような牛を避けるでしょう。 – jalf

+2

私は標準では、メンバー関数のイテレーターの無効化要件のためにCOWも許可しないと思います。 – Xeo

+4

Afaik C++ 03はCOWを許可しています。私はC++ 11がそれを禁じていると信じています – jalf

答えて

10

問題がのstd :: stringクラスはconst char*ポインタがリテラルかグローバルな文字であるかどうかを認識するための方法がないことです(例えば、ローカル変数で関数/スコープが終了する場合)std::stringは、文字列の排他的な所有者になる必要があります。これはコピーすることによってのみ達成できます。それが必要な理由

次の例は示しています

std::string getHelloWorld() { 
    char *hello = new char[64]; 
    strcpy(hello, "Hello World"); 
    std::string result = (const char *)hello; // If std::string didn't make a copy, the result could be a garbage 
    delete[] hello; 
    return result; 
} 
+0

実際には、文字列リテラルは 'char [N]'です。ここでNは長さ+ 1(ヌル終端文字)です。 – Xeo

+2

なぜ 'new'ですか? 'char const hello [] =" Hello World ";' –

+1

@MatthieuM .:あなたのバージョンは例外的に安全ですが、dark_charlieは文字列コンストラクタがno-スロー。 –

5

std::stringは銀の弾丸ではありません。これは、メモリを所有し、C APIで使用するのに適度に安い汎用の可変文字列を可能な限り実装することを意図しています。これらは一般的なシナリオですが、文字列使用のインスタンスごとにと一致しません。

あなたが言及しているように、文字列リテラルはこのユースケースにうまく適合しません。彼らは静的に割り当てられたメモリを使用するので、std::stringはメモリの所有権を取ることはできません。これらの文字列はで、常にの読み取り専用なので、std::stringで変更することはできません。

std::stringは、渡された文字列データのコピーを作成し、このコピーを内部的に処理します。

他の場所で処理される定数文字列(文字列リテラルの場合、静的データを初期化および解放するランタイムライブラリによって処理される)で操作する場合は、別の文字列表現を使用することができます。たぶん簡単なconst char*

+0

です。*メモリを所有する可変文字列の実装が可能です。程遠い。 '.c_str() 'コストによって厳格な要件が課されます。文字列が大きく、変更された場合、コストのかかる再割り当てを避けるために、B-Treesを使用するのが最適な実装です。 –

+0

しかしそれは連続性を失い、C文字列への変換がよりコストがかかることになります。考慮すべき多くのトレードオフがあります。 :)しかし私は少し私の答えを明らかにした。 – jalf

+0

はい、これが私が 'c_str'に言及した理由です。私はSGI STLがCの相互作用が必要でない場合をカバーするために 'ロープ'クラスを持っていたと信じています。 –

21

リテラルを変更した場合、実際には、あなたの悩みは、(*)離れて行くだろう:

std::string const SL = "Hello to all!"; 

私はあなたのためconstを追加しました。今

foo1を呼び出すと(すべての)いずれかのコピーを伴わないだろう、と少しコストで実現することができfoo2を呼び出す:

foo1(SL);   // by const-reference, exact same cost than a pointer 
foo2(SL.c_str()); // simple pointer 

あなたはstd::stringに移動したい場合は、関数だけを切り替えないでくださいインターフェイス、変数(および定数)も切り替えます。

(*)元の回答では、グローバル定数の場合はSLと仮定していましたが、関数呼び出し時にビルドするのを避けたい場合はstaticにすることができます。

+0

追加ポイント:文字列リテラルが関数内にある場合は、静的にすることができます。 –

+4

起動時にすべての文字列リテラルがヒープにコピーされることはありませんか? –

+0

なぜ心配がなくなるのですか?さて、std :: stringオブジェクトは、入力/終了スコープで構築/破壊されます。これにより、以前と同じメモリのalloc/dealloc(std :: stringの実装に依存します)が発生する可能性があります。それを静的にしても静的にならないのですか? –