35

テンポラリーを非 const 参照にバインドできないことはわかっていますが、const 参照にはバインドできます。あれは、

 A & x = A(); //error
 const A & y = A(); //ok

A()また、2 番目のケース (上記) では、作成された一時オブジェクトの有効期間が、const 参照の有効期間 (つまり ) まで延長されることも知っていyます。

しかし、私の質問は次のとおりです。

一時オブジェクトにバインドされている const 参照をさらに別の const 参照にバインドして、一時オブジェクトの有効期間を 2 番目のオブジェクトの有効期間まで延長できますか?

私はこれを試しましたが、うまくいきませんでした。私はこれを正確に理解していません。私はこのコードを書きました:

struct A
{
   A()  { std::cout << " A()" << std::endl; }
   ~A() { std::cout << "~A()" << std::endl; }
};

struct B
{
   const A & a;
   B(const A & a) : a(a) { std::cout << " B()" << std::endl; }
   ~B() { std::cout << "~B()" << std::endl; }
};

int main() 
{
        {
            A a;
            B b(a);
        }
        std::cout << "-----" << std::endl;
        {
            B b((A())); //extra braces are needed!
        }
}

出力 ( ideone ):

 A()
 B()
~B()
~A()
-----
 A()
 B()
~A()
~B()

出力の違い?2 番目のケースで一時オブジェクトA()がオブジェクトの前に破棄されるのはなぜですか? b標準 (C++03) はこの動作について話していますか?

4

7 に答える 7

24

標準では、テンポラリーの存続期間が延長される 2 つの状況が考慮されています。

§12.2/4 完全式の終わりとは異なる時点で一時変数が破棄されるコンテキストが 2 つあります。最初のコンテキストは、オブジェクトを定義する宣言子の初期化子として式が現れる場合です。そのコンテキストでは、式の結果を保持する一時オブジェクトは、オブジェクトの初期化が完了するまで持続します。[...]

§12.2/5 2 番目のコンテキストは、参照が一時にバインドされている場合です。[...]

これらの 2 つのいずれも、別の const 参照への参照を後でバインドすることによって、一時オブジェクトの有効期間を延長することはできません。しかし、標準語を無視して、何が起こっているかを考えてください。

一時はスタックに作成されます。技術的には、呼び出し規約は、レジスタに収まる戻り値 (一時的) がスタック内に作成されない可能性があることを意味している可能性がありますが、我慢してください。定数参照を一時変数にバインドすると、コンパイラは意味的に隠し名前付き変数を作成し (そのため、たとえ呼び出されなくてもコピー コンストラクターにアクセスできる必要があります)、その変数への参照をバインドします。コピーが実際に作成されるか省略されるかは詳細です。ここにあるのは、名前のないローカル変数とそれへの参照です。

標準でユースケースが許可されている場合、一時変数の有効期間は、その変数への最後の参照までずっと延長する必要があることを意味します。次に、例のこの単純な拡張について考えてみましょう。

B* f() {
   B * bp = new B(A());
   return b;
}
void test() {
   B* p = f();
   delete p;
}

ここでの問題は、一時的な (それを呼び出すことができます_T) が にバインドされf()ていることです。これは、そこでローカル変数のように動作します。参照は 内でバインドされ*bpます。オブジェクトの有効期間は、一時オブジェクトを作成した関数を超えて延長されますが、_T動的に割り当てられていないため、これは不可能です。

この例でテンポラリーの寿命を延ばすために必要な労力を試してみると、その答えは、なんらかの形式の GC なしでは実行できないということです。

于 2011-08-04T07:41:22.863 に答える
8

いいえ、延長された有効期間は、参照を渡すことによってさらに延長されることはありません。

2 番目のケースでは、テンポラリはパラメーターa にバインドされ、パラメーターの有効期間の終わり (コンストラクターの終わり) に破棄されます。

標準は明示的に次のように述べています。

コンストラクターの ctor-initializer (12.6.2) の参照メンバーへの一時的なバインドは、コンストラクターが終了するまで持続します。

于 2011-08-04T06:27:48.613 に答える
5

§12.2/5は、「[一時的なものの存続期間が延長されるときの] 2番目のコンテキストは、参照が一時的なものにバインドされるときです」と述べています。 文字通り、これはあなたの場合、寿命を延ばすべきであることを明確に示しています。あなたB::aは確かに一時的なものに縛られています。(参照はオブジェクトにバインドされますが、バインドされる可能性のある他のオブジェクトは表示されません。)ただし、これは非常に不適切な表現です。「2番目のコンテキストは、一時的なものを使用して参照を初期化する場合です」という意味だと確信しています。延長された存続期間は、一時値を作成する右辺値式で開始された参照の存続期間に対応し、後でオブジェクトにバインドされる可能性のある他の参照の存続期間には対応しません。現状では、この表現には、単に実装できないものが必要です。次のことを考慮してください。

void f(A const& a)
{
    static A const& localA = a;
}

で呼び出されます:

f(A());

コンパイラはどこに置くべきA()ですか(通常、のコードを見ることができずf()、呼び出しを生成するときにローカル静的について知らない場合)?

実際、これはDRの価値があると思います。

私の意図の解釈が正しいことを強く示唆するテキストがあることを付け加えるかもしれません。次の2番目のコンストラクターがあると想像してくださいB

B::B() : a(A()) {}

この場合、B::aは一時的なもので直接初期化されます。この一時的なものの寿命は、私の解釈によっても延長されるべきです。ただし、この標準では、この場合に特定の例外があります。このような一時的なものは、コンストラクターが終了するまでしか持続しません(これにより、参照がぶら下がっています)。この例外は、標準の作成者が、クラス内のメンバー参照がバインドされている一時的なものの存続期間を延長することを意図していなかったことを非常に強く示しています。繰り返しますが、動機は実装可能性です。代わりにそれを想像してみてください

B b((A()));

あなたが書いた:

B* b = new B(A());

A()コンパイラは、動的に割り当てられたライフタイムになるように、一時的なものをどこに配置する必要がありますBか?

于 2011-08-04T08:25:54.517 に答える
4

あなたの例は、ネストされた有効期間の延長を実行しません

コンストラクタで

B(const A & a_) : a(a_) { std::cout << " B()" << std::endl; }

ここa_(博覧会のために改名) は一時的なものではありません。式が一時的であるかどうかは式の構文上のプロパティであり、id-expressionは一時的ではありません。したがって、ここでは有効期間の延長は発生しません。

有効期間の延長が発生するケースは次のとおりです。

B() : a(A()) { std::cout << " B()" << std::endl; }

ただし、参照は ctor-initializer で初期化されるため、有効期間は関数の最後までしか延長されません。[ class.temporary ]p5 あたり:

コンストラクターの ctor -initializer (12.6.2)の参照メンバーへの一時的なバインドは、コンストラクターが終了するまで持続します。

コンストラクターの呼び出しで

B b((A())); //extra braces are needed!

ここでは一時への参照をバインドしています。[class.temporary]p5言います:

関数呼び出し (5.2.2) の参照パラメーターへの一時的なバインドは、呼び出しを含む完全な式が完了するまで持続します。

したがって、Aテンポラリはステートメントの最後で破棄されます。これは、B変数がブロックの最後で破棄される前に発生し、ログ出力を説明しています。

他のケースでは、ネストされた有効期間の延長を実行します

集計変数の初期化

参照メンバーを持つ構造体の集約初期化は、有効期間を延長できます。

struct X {
  const A &a;
};
X x = { A() };

この場合、Aテンポラリは参照に直接バインドされるため、テンポラリは のライフタイムx.aと同じであるのライフタイムまで存続期間が延長されxます。(警告: 最近まで、これを正しく行っているコンパイラはほとんどありませんでした)。

集約一時初期化

C++11 では、集約初期化を使用して一時を初期化し、再帰的な有効期間の延長を取得できます。

struct A {
   A()  { std::cout << " A()" << std::endl; }
   ~A() { std::cout << "~A()" << std::endl; }
};

struct B {
   const A &a;
   ~B() { std::cout << "~B()" << std::endl; }
};

int main() {
  const B &b = B { A() };
  std::cout << "-----" << std::endl;
}

トランク Clang または g++ を使用すると、次の出力が生成されます。

 A()
-----
~B()
~A()

A一時的なものとB一時的なものはどちらも存続期間が延長されることに注意してください。A一時的なものの構築が最初に完了するため、最後に破棄されます。

std::initializer_list<T>初期化中

C++11std::initializer_list<T>は、基になる配列への参照をバインドするかのように、有効期間の延長を実行します。したがって、 を使用してネストされた有効期間の延長を実行できますstd::initializer_list。ただし、この領域ではコンパイラのバグがよく見られます。

struct C {
  std::initializer_list<B> b;
  ~C() { std::cout << "~C()" << std::endl; }
};
int main() {
  const C &c = C{ { { A() }, { A() } } };
  std::cout << "-----" << std::endl;
}

Clang トランクで生成します:

 A()
 A()
-----
~C()
~B()
~B()
~A()
~A()

および g++ トランクを使用する場合:

 A()
 A()
~A()
~A()
-----
~C()
~B()
~B() 

これらはどちらも間違っています。正しい出力は次のとおりです。

 A()
 A()
-----
~C()
~B()
~A()
~B()
~A()
于 2013-06-27T23:04:37.450 に答える
2

最初の実行では、オブジェクトはスタックにプッシュされた順序で破棄されます->つまり、プッシュA、プッシュB、ポップB、ポップAです。

2回目の実行では、Aの存続期間はbの構築で終了します。したがって、Aを作成し、AからBを作成し、Aの存続期間が終了して破棄され、次にBが破棄されます。理にかなっています...

于 2011-08-04T06:20:40.033 に答える
1

標準についてはわかりませんが、以前のいくつかの質問で見たいくつかの事実について説明できます。

a最初の出力は、とbが同じスコープ内にあるという明らかな理由からそのままです。また 、前に構築されているため、a後で破棄されます。bb

2番目の出力にもっと興味があるはずです。始める前に、次の種類のオブジェクトの作成 (スタンドアローンの一時オブジェクト) に注意する必要があります。

{
  A();
}

周囲のブロックではなく;、次のブロックまでしか持続しません。デモ。あなたの2番目のケースでは、そうするとき、

B b((A()));

したがって、オブジェクトの作成が完了A()するとすぐに破棄されます。B()const 参照は一時的にバインドできるため、コンパイル エラーは発生しません。B::aただし、既にスコープ外の変数にバインドされているにアクセスしようとすると、確実に論理エラーが発生します。

于 2011-08-04T06:34:37.390 に答える
-1

§12.2/5 は言う

関数呼び出し (5.2.2) の参照パラメーターへの一時的なバインドは、呼び出しを含む完全な式が完了するまで持続します。

本当に、かなりカットして乾燥させました。

于 2011-08-07T23:39:25.103 に答える