首页 > 解决方案 > How unused default member initializer can change program behavior in C++?

问题描述

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?

标签: c++c++20aggregate-initialization

解决方案


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{{}};

推荐阅读