Your example doesn't perform nested lifetime extension
In the constructor
B(const A & a_) : a(a_) { std::cout << " B()" << std::endl; }
The a_
here (renamed for exposition) is not a temporary. Whether an expression is a temporary is a syntactic property of the expression, and an id-expression is never a temporary. So no lifetime extension occurs here.
Here's a case where lifetime-extension would occur:
B() : a(A()) { std::cout << " B()" << std::endl; }
However, because the reference is initialized in a ctor-initializer, the lifetime is only extended until the end of the function. Per [class.temporary]p5:
A temporary bound to a reference member in a constructor's ctor-initializer (12.6.2) persists until the constructor exits.
In the call to the constructor
B b((A())); //extra braces are needed!
Here, we are binding a reference to a temporary. [class.temporary]p5 says:
A temporary bound to a reference parameter in a function call (5.2.2) persists until the completion of the full-expression containing the call.
Therefore the A
temporary is destroyed at the end of the statement. This happens before the B
variable is destroyed at the end of the block, explaining your logging output.
Other cases do perform nested lifetime extension
Aggregate variable initialization
Aggregate initialization of a struct with a reference member can lifetime-extend:
struct X {
const A &a;
};
X x = { A() };
In this case, the A
temporary is bound directly to a reference, and so the temporary is lifetime-extended to the lifetime of x.a
, which is the same as the lifetime of x
. (Warning: until recently, very few compilers got this right).
Aggregate temporary initialization
In C++11, you can use aggregate initialization to initialize a temporary, and thus get recursive lifetime extension:
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;
}
With trunk Clang or g++, this produces the following output:
A()
-----
~B()
~A()
Note that both the A
temporary and the B
temporary are lifetime-extended. Because the construction of the A
temporary completes first, it is destroyed last.
In std::initializer_list<T>
initialization
C++11's std::initializer_list<T>
performs lifetime-extension as if by binding a reference to the underlying array. Therefore we can perform nested lifetime extension using std::initializer_list
. However, compiler bugs are common in this area:
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;
}
Produces with Clang trunk:
A()
A()
-----
~C()
~B()
~B()
~A()
~A()
and with g++ trunk:
A()
A()
~A()
~A()
-----
~C()
~B()
~B()
These are both wrong; the correct output is:
A()
A()
-----
~C()
~B()
~A()
~B()
~A()