Reputation: 21221
Please consider this short code example:
#include <iostream>
struct A
{
A() { std::cout << "A() "; }
~A() { std::cout << "~A() "; }
};
struct B { const A &a; };
struct C { const A &a = {}; };
int main()
{
B b({});
std::cout << ". ";
C c({});
std::cout << ". ";
}
GCC prints here ( https://gcc.godbolt.org/z/czWrq8G5j )
A() ~A() . A() . ~A()
meaning that the lifetime of A
-object initializing reference in b
is short, but in c
the lifetime is prolonged till the end of the scope.
The only difference between structs B
and C
is in default member initializer, which is unused in main(), still the behavior is distinct. Could you please explain why?
Upvotes: 7
Views: 191
Reputation: 238351
C c(...);
is syntax for direct initialisation. Overload resolution would find a match from the constructors of C
: The move constructor can be called by temporary materialisation of a C
from {}
. {}
is value initialisation which will use the default member initialiser. Thus, the default member initialiser isn't unused. Since C++17, the move constructor isn't necessary and {}
initialises variable c
directly; In this case c.a
is bound directly to the temporary A
and the lifetime is extended until destruction of C
.
B
isn't default constructible, so the overload resolution won't find a match. Instead, aggregate initialisation is used since C++20 - prior to that it would be ill-formed. The design of the C++20 feature is to not change behaviour of previously valid programs, so aggregate initialisation has lower priority than the move constructor.
Unlike in the case of C
, the lifetime of the temporary A
isn't extended because parenthesised initialisation list is an exceptional case. It would be extended if you used curly braces:
B b{{}};
Upvotes: 1