2009-09-03 21 views
120

私の質問は、stringstream.str().c_str()から返された文字列はどこにありますか?そしてそれはなぜconst char*に割り当てられないのですか?stringstream、string、char *変換の混乱

このコード例では、私が

でき
#include <string> 
#include <sstream> 
#include <iostream> 

using namespace std; 

int main() 
{ 
    stringstream ss("this is a string\n"); 

    string str(ss.str()); 

    const char* cstr1 = str.c_str(); 

    const char* cstr2 = ss.str().c_str(); 

    cout << cstr1 // Prints correctly 
     << cstr2; // ERROR, prints out garbage 

    system("PAUSE"); 

    return 0; 
} 

より良いstringstream.str().c_str()を追跡するために私にしばらく時間がかかったバグにつながっconst char*に割り当てることができることを前提にそれを説明します。

cout << cstr   // Prints correctly 
    << ss.str().c_str() // Prints correctly 
    << cstr2;   // Prints correctly (???) 

が正しく文字列を印刷してcout文を交換する理由ボーナスポイント

は、誰もが説明できますか?

私は、Visual Studioでコンパイルしています2008年

答えて

172

stringstream.str()は、完全な式の最後に破棄される一時的な文字列オブジェクトを返します。その(stringstream.str().c_str())からCの文字列へのポインタを取得した場合は、文が終わるところで削除される文字列を指します。だからあなたのコードはゴミを印刷します。

あなたは、いくつかの他の文字列オブジェクトにその一時的な文字列オブジェクトをコピーして、その1からC文字列を取ることができる:それへの変更は、それを引き起こす可能性があるので、私は、一時的な文字列constを作っ

const std::string tmp = stringstream.str(); 
const char* cstr = tmp.c_str(); 

注意再割り当てし、したがってcstrを無効にします。

use_c_str(stringstream.str().c_str()); 

もちろん、後者は簡単ではないかもしれないとコピーがあまりにもあるかもしれない:すべてでstr()への呼び出しの結果を格納し、唯一の完全な表現の終わりまでcstrを使用しないようにするためにその方が安全です高価な。代わりに、constの参照に一時的なものをバインドすることができます。これは、参照の寿命にその寿命を延長します:

{ 
    const std::string& tmp = stringstream.str(); 
    const char* cstr = tmp.c_str(); 
} 

はIMOそれが最善の解決策です。残念ながらそれはあまりよく知られていません。この行で

+12

'str()'がRVOが実行されるように実装されていると(おそらくそうです)、コピーを実行すると(最初の例のように)オーバーヘッドが必ずしも発生しないことに注意してください。コンパイラは結果を 'tmp'に直接組み込み、一時的なものを取り除くことができます。最新のC++コンパイラは、最適化が有効になっているときにこれを行います。もちろん、バインド・ツー・コンスタント・リファレンス・ソリューションはコピーを保証しないので、望ましいかもしれませんが、それでも明らかにする価値はあると思いました。 –

+1

"もちろん、バインド・ツー・コンスタント・リファレンス・ソリューションはコピーを保証しません。 C++ 03では、コピーコンストラクタにアクセス可能である必要があり、実装で初期化子をコピーして参照をコピーにバインドすることができます。 –

+1

あなたの最初の例は間違っています。 c_str()によって返される値は一時的です。現在のステートメントの終了後には信頼できません。したがって、それを使って関数に値を渡すことはできますが、c_str()の結果をローカル変数に割り当ててはいけません。 –

12

あなたが一時的に作成されてやっています。その一時的なものは、コンパイラによって決定されたスコープ内に存在するため、実行される場所の要件を満たすのに十分な長さです。

ステートメントconst char* cstr2 = ss.str().c_str();が完了すると、コンパイラは一時的な文字列を保持する理由がなくなり、破棄されます。したがって、const char *は空きメモリを指しています。

あなたの声明string str(ss.str());は一時的にローカルスタックに置かれているstring変数strのコンストラクタで使用されていることを意味し、それは限り、あなたが期待するよう周囲にとどまる:ブロックの終わりまで、またはあなたが書いた機能です。そのため、を試してみると、const char *の内部メモリは良好です。

5

const char* cstr2 = ss.str().c_str(); 

ss.str()はにstringstreamの内容のコピーを行います。同じ行にc_str()を呼び出すと正当なデータを参照しますが、その行の後に文字列は破棄され、char*は所有されていないメモリを指しています。

4

ss.str()によって返されるstd :: stringオブジェクトは、式に限定されたライフタイムを持つ一時オブジェクトです。したがって、ゴミ箱を持たずに一時オブジェクトにポインタを割り当てることはできません。

ここでは例外が1つあります。つまり、一時参照を取得するためにconst参照を使用する場合は、それをより長い期間使用することは合法です。たとえば、次のようにする必要があります。

#include <string> 
#include <sstream> 
#include <iostream> 

using namespace std; 

int main() 
{ 
    stringstream ss("this is a string\n"); 

    string str(ss.str()); 

    const char* cstr1 = str.c_str(); 

    const std::string& resultstr = ss.str(); 
    const char* cstr2 = resultstr.c_str(); 

    cout << cstr1  // Prints correctly 
     << cstr2;  // No more error : cstr2 points to resultstr memory that is still alive as we used the const reference to keep it for a time. 

    system("PAUSE"); 

    return 0; 
} 

こうすると、長い時間文字列が得られます。

コンパイラが関数呼び出しによる初期化を参照し、その関数が一時的な結果を返す場合、コピーを実行せずに代入を行うだけであるというRVOという最適化があることを知っておく必要があります値は一時的です。あなたが実際に参照を使用する必要はありません、それはそれが必要であることをコピーしないことを確認したい場合のみです。そうすること:

std::string resultstr = ss.str(); 
const char* cstr2 = resultstr.c_str(); 

は、より簡単で簡単です。

5

ss.str()cstr2の初期化が完了すると一時的に破棄されます。したがって、coutで印刷すると、一時的にstd::stringと関連付けられたC文字列が破棄されてしまい、クラッシュしてアサートしてしまった場合には運が良かったり、ゴミを印刷したり、

const char* cstr2 = ss.str().c_str(); 

cstr1ポイントは、しかし、まだあなたがcoutを行う時に存在する文字列に関連付けられているC-文字列 - それは正しく結果を出力します。

次のコードでは、最初のcstrが正しいです(実際のコードではcstr1と仮定しています)。 2番目の文字列は、テンポラリ文字列オブジェクトss.str()に関連付けられたc-stringを出力します。オブジェクトは、それが現れる完全表現を評価し終わった時点で破棄されます。完全な式はcout << ...の式全体です - したがって、C文字列が出力されている間は、関連する文字列オブジェクトはまだ存在します。 cstr2 - それは成功する純粋な悪です。 cstr2の初期化に使用された一時的なもののために既に選択した新しい一時的なものと同じ記憶場所を内部で選択する可能性が最も高いです。それもクラッシュする可能性があります。 c_str()

cout << cstr   // Prints correctly 
    << ss.str().c_str() // Prints correctly 
    << cstr2;   // Prints correctly (???) 

リターンは、通常は内部文字列バッファを指します - それは必要条件ではありません。文字列は、その内部実装が連続していない場合(例えば可能ですが、次のC++標準では文字列を連続して格納する必要があります)、バッファを構成できます。

GCCでは、文字列は参照カウントとコピーオンライトを使用します。このように、あなたは以下が成り立つことがわかります(それは、少なくとも私のGCCのバージョンに、ありません)

string a = "hello"; 
string b(a); 
assert(a.c_str() == b.c_str()); 

2つの文字列が、ここで同じバッファを共有しています。そのうちの1つを変更すると、バッファーがコピーされ、それぞれに別個のコピーが保持されます。しかし、他の文字列の実装では、さまざまなことが行われます。