Search code examples
c++defaultdefault-constructorvariable-initializationmember-initialization

What Form Does the Implicitly-Declared Default Constructor Take?


So let's say I'm working with this toy example:

struct Foo {
    int member;
};

I know that the default constructor won't default initialize member. So if I do this, member remains uninitialized: const Foo implicit_construction. As an aside this seems to work fine: const Foo value_initialization = Foo() though my understanding is that this isn't actually using the default constructor.

If I change Foo like this:

struct Foo {
    Foo() = default;
    int member;
};

And I try to do const Foo defaulted_construction, unsurprisingly it behaves exactly as implicit_construction did, with member being uninitialized.

Finally, if I change Foo to this:

struct Foo {
    Foo(){};
    int member;
};

And I do: const Foo defined_construction member is zero-initialized. I'm just trying to make sense of what the implicitly defined constructor looks like. I would have though it would have been Foo(){}. Is this not the case? Is there some other black magic at work that makes my defined constructor behave differently than the defaulted one?


Edit:

Perhaps I'm being mislead here. defaulted_construction is definitely uninitialized.
While defined_construction is definitely initialized.

I took this to be standardized behavior, is that incorrect?


Solution

  • What you're experiencing is called default initialization and the rules for it are (emphasis mine):

    • if T is a non-POD (until C++11) class type, the constructors are considered and subjected to overload resolution against the empty argument list. The constructor selected (which is one of the default constructors) is called to provide the initial value for the new object;
    • if T is an array type, every element of the array is default-initialized;
    • otherwise, nothing is done: the objects with automatic storage duration (and their subobjects) are initialized to indeterminate values.

    Edit, in response to OP's request below:

    Note that declaring a constructor = default does not change the situation (again, emphasis mine):

    Implicitly-defined default constructor

    If the implicitly-declared default constructor is not defined as deleted, it is defined (that is, a function body is generated and compiled) by the compiler if odr-used, and it has exactly the same effect as a user-defined constructor with empty body and empty initializer list. That is, it calls the default constructors of the bases and of the non-static members of this class.

    Since the default constructor has an empty initializer list, its members satisfy the conditions for default initialization:

    Default initialization is performed in three situations:

    ...

    3) when a base class or a non-static data member is not mentioned in a constructor initializer list and that constructor is called.

    Also note that you have to be careful when experimentally confirming this, because it's entirely possible that the default-initialized value of an int may be zero. In particular, you mentioned that this:

    struct Foo {
        Foo(){};
        int member;
    } foo;
    

    Results in value-initialization, but it does not; here, member is default-initialized.

    Edit 2:

    Note the following distinction:

    struct Foo {
        int member;
    };
    
    Foo a; // is not value-initialized; value of `member` is undefined
    Foo b = Foo(); // IS value-initialized; value of `member` is 0
    

    This behavior can be understood by following the rules for value-initialization:

    Value initialization is performed in these situations:

    1,5) when a nameless temporary object is created with the initializer consisting of an empty pair of parentheses;

    Form 1 (T();) is the form used on the right-hand side of the = above to initialize b.

    The effects of value initialization are:

    1) if T is a class type with no default constructor or with a user-provided or deleted default constructor, the object is default-initialized;

    2) if T is a class type with a default constructor that is neither user-provided nor deleted (that is, it may be a class with an implicitly-defined or defaulted default constructor), the object is zero-initialized and then it is default-initialized if it has a non-trivial default constructor;

    3) if T is an array type, each element of the array is value-initialized;

    4) otherwise, the object is zero-initialized.

    Finally though, note that in our earlier example:

    struct Foo {
        Foo(){}; // this satisfies condition (1) above
        int member;
    };
    
    Foo f = Foo();
    

    Now, condition (1) applies, and our (empty) user-declared constructor is called instead. Since this constructor does not initialize member, member is default-initialized (and its initial value is thus undefined).