const参照クラスのメンバーは一時的なオブジェクトの寿命を延ばしますか?


171

これはなぜですか:

#include <string>
#include <iostream>
using namespace std;

class Sandbox
{
public:
    Sandbox(const string& n) : member(n) {}
    const string& member;
};

int main()
{
    Sandbox sandbox(string("four"));
    cout << "The answer is: " << sandbox.member << endl;
    return 0;
}

出力を与える:

答えは:

の代わりに:

答えは:4


39
そしてもっと面白くするために、あなたが書いたならばcout << "The answer is: " << Sandbox(string("four")).member << endl;、それは動作することが保証されます。

7
@RogerPate理由を説明してもらえますか?
Paolo M

16
好奇心旺盛な誰かのために、例えば、ロジャー・パテはので、作品を掲示文字列(「4」) 、一時的かつ一時的に破壊されていることであるフルexpresionの終わりにときに彼の例では、SandBox::member読み込まれ、一時的な文字列がまだ生きています
PcAF 2016年

1
問題は次のとおりです。そのようなクラスを記述することは危険であるため、そのようなクラス一時を渡すことに対するコンパイラの警告はありますか、または参照を格納するクラスの記述を禁止する設計ガイドライン(Stroustroup内)がありますか?参照の代わりにポインタを格納する設計ガイドラインの方が良いでしょう。
Grim Fandango

@PcAF:コンストラクタが終了したstring("four")後ではなく、一時式が完全な式の最後に破棄される理由を説明していただけSandboxますか?Potatoswatterの回答によると、コンストラクターのctor-initializer(§12.6.2[class.base.init])の参照メンバーへの一時的なバインドは、コンストラクターが終了するまで持続します。
テイラーニコルズ

回答:


166

ローカル const参照のみが寿命を延ばします。

規格は、§8.5.3/ 5、[dcl.init.ref]、参照宣言の初期化子に関するセクションでそのような動作を指定しています。あなたの例の参照はコンストラクタの引数にバインドされておりn、オブジェクトがnがバインドされてスコープ外に。

存続期間の延長は、関数の引数を通じて推移的ではありません。§12.2/ 5 [class.temporary]:

2番目のコンテキストは、参照が一時にバインドされている場合です。参照がバインドされている一時オブジェクト、または一時オブジェクトがサブオブジェクトの完全なオブジェクトである一時オブジェクトは、以下に指定されている場合を除いて、参照の存続期間中存続します。コンストラクターのctor-initializer(§12.6.2[class.base.init])の参照メンバーへの一時的なバインドは、コンストラクターが終了するまで存続します。関数呼び出し(§5.2.2[expr.call])の参照パラメーターに一時的にバインドされたものは、呼び出しを含む完全な式が完了するまで存続します。


49
より人間にやさしい説明については、GotW#88もご覧ください。herbsutter.com/ 2008/01/01
Nathan Ernst

1
標準が「2番目のコンテキストは、参照がprvalueにバインドされている場合です」と言っていれば、より明確になると思います。OPのコードでは、あなたがそれを言うことができるmember一時的にバインドされ、初期化ためmembern結合することを意味member同じオブジェクトをnにバインドされ、それは実際にこの場合の一時的なオブジェクトです。
2016年

2
@MM prvalueを含むlvalueまたはxvalue初期化子がprvalueを拡張する場合があります。私の提案書P0066は状況をレビューしています。
Potatoswatter 2016年

1
C ++ 11以降、Rvalue参照は、修飾子を必要とせずに一時const変数の寿命を延ばします。
GetFree

3
@KeNVinFavoはい、死んだオブジェクトの使用は常にUB
Potatoswatter

30

何が起こったかを説明する最も簡単な方法は次のとおりです。

main()で文字列を作成し、それをコンストラクタに渡しました。この文字列インスタンスは、コンストラクター内にのみ存在しました。コンストラクター内で、このインスタンスを直接指すようにメンバーを割り当てました。スコープがコンストラクターを離れると、文字列インスタンスが破棄され、メンバーは存在しなくなった文字列オブジェクトをポイントしました。Sandbox.memberがスコープ外の参照を指すようにしても、それらの外部インスタンスはスコープに保持されません。

必要な動作を表示するようにプログラムを修正する場合は、次の変更を加えます。

int main()
{
    string temp = string("four");    
    Sandbox sandbox(temp);
    cout << sandbox.member << endl;
    return 0;
}

tempは、コンストラクタの最後ではなく、main()の最後でスコープ外に渡されます。ただし、これは悪い習慣です。メンバー変数は、インスタンスの外部に存在する変数への参照であってはなりません。実際には、いつその変数がスコープ外になるかはわかりません。

Sandbox.memberをとして定義することをお勧めします。const string member;これにより、メンバー変数を一時パラメーター自体として割り当てる代わりに、一時パラメーターのデータがメンバー変数にコピーされます。


これを行った場合:const string & temp = string("four"); Sandbox sandbox(temp); cout << sandbox.member << endl;それでも機能しますか?
イブ

@Thomasは、const string &temp = string("four");同じ結果を与えるconst string temp("four"); あなたが使用しない限り、decltype(temp)特に
MM

@MMありがとうございます。この質問は完全に理解できました。
イヴ

However, this is bad practice.- なぜ?tempとそれを含むオブジェクトの両方が同じスコープで自動ストレージを使用する場合、100%安全ではありませんか?そして、それを行わない場合、文字列が大きすぎてコピーするには高すぎるため、どうしますか?
最大

2
@max、クラスは正しいスコープを持つように一時的に渡されたものを強制しないため。つまり、この要件を忘れて、無効な一時値を渡すと、コンパイラーが警告を出さなくなる可能性があります。
Alex Che

5

技術的に言えば、このプログラムは標準出力(最初はバッファリングされたストリーム)に実際に何かを出力する必要はありません。

  • cout << "The answer is: "ビットが放出する"The answer is: "バッファ STDOUTの。

  • 次に、<< sandbox.memberビットはにぶら下がる参照を提供しoperator << (ostream &, const std::string &)未定義の動作を呼び出します。

このため、何も起こらないことが保証されています。プログラムは一見正常に機能するか、標準出力をフラッシュすることなくクラッシュする可能性があります。つまり、「答えは:」というテキストが画面に表示されません。


2
UBがある場合、プログラム全体の動作は定義されていません-実行の特定の時点で開始するだけではありません。だから、"The answer is: "どこにでも書けるとは言えない。
Toby Speight 2018年

0

サンドボックスコンストラクターが戻ると一時的な文字列がスコープ外になり、それによって占められていたスタックが他の目的で再利用されたためです。

通常、参照を長期間保持することはできません。参照は、クラスメンバーではなく、引数またはローカル変数に適しています。


7
「決してない」はひどく強い言葉です。
Fred Larson、

17
オブジェクトへの参照を保持する必要がある場合を除いて、クラスメンバーは決してありません。コピーではなく他のオブジェクトへの参照を保持する必要がある場合があります。これらの場合、参照はポインターよりも明確なソリューションです。
デビッドロドリゲス-10

0

あなたは消えた何かを指しているのです。以下は動作します

#include <string>
#include <iostream>

class Sandbox
{

public:
    const string member = " "; //default to whatever is the requirement
    Sandbox(const string& n) : member(n) {}//a copy is made

};

int main()
{
    Sandbox sandbox(string("four"));
    std::cout << "The answer is: " << sandbox.member << std::endl;
    return 0;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.