Search code examples
c++constructorstandardsc++14default-constructor

Differences between several ways to declare an empty/default constructor


In C++14 there are several ways to declare an empty constructor

class C1 {
    int* ptr;
    int val;
};

class C2 {
    int* ptr = nullptr;
    int val = 0;
};

class C3 {
    constexpr C3() noexcept = default;
    int* ptr;
    int val;
};

class C4 {
    constexpr C4() noexcept = default;
    int* ptr = nullptr;
    int val = 0;
};

class C5 {
    constexpr C5() noexcept : ptr{nullptr}, val{0} = default;
    int* ptr;
    int val;
};

class C6 {
    constexpr C6() noexcept : ptr{nullptr}, val{0} {}
    int* ptr;
    int val;
};

class C7 {
    constexpr C7() noexcept;
    int* ptr;
    int val;
};
constexpr C7::C7() noexcept = default;

class C8 {
    constexpr C8() noexcept;
    int* ptr = nullptr;
    int val = 0;
};
constexpr C8::C8() noexcept = default;

class C9 {
    constexpr C9() noexcept;
    int* ptr;
    int val;
};
constexpr C9::C9() noexcept : ptr{nullptr}, val{0} = default;

class C10 {
    constexpr C10() noexcept;
    int* ptr;
    int val;
};
constexpr C10::C10() noexcept : ptr{nullptr}, val{0} {}

I am wondering, what are the exact differences between all these classes and what classes are strictly equivalent, and will produce the exact same behaviour according to the C++ standard.


Solution

  • class C1 {
        int* ptr;
        int val;
    }
    

    The compile will declare and define a public trivial noexcept default ctor. Being trivial, it won't perform any initialization of the members. A user can choose between default-initialization (which won't perform any init of the data members) or value-initialization (which will zero-initialize the data members):

    C1 x;        // default-initialized
    C1 y = C1(); // value-initialized
    

    A trivial default ctor has an influence on object lifetime and the PODness of the class.

    If C1 were a struct (that is, the data members being public), it would be an aggregate:

    C1 a {nullptr, 42};
    C1 z{};      // aggregate-initialized, but same effects as for y
    

    Despite the default ctor not being constexpr, you can create instances of this class within constant expressions: Being trivial, the default ctor won't be called in value-initialization (nor in aggregate-initialization).


    class C2 {
        int* ptr = nullptr;
        int val = 0;
    };
    

    The compiler will declare and define a public constexpr and noexcept default ctor, which initializes the data members according to their NSDMIs (non-static data member initializers, i.e. the = x;). Both default- and value-initialization will call the default ctor and initialize the members. A struct C2 would be an aggregate according to C++14 rules.


    class C3 {
        constexpr C3() noexcept = default;
        int* ptr;
        int val;
    };
    

    Is illegal, because constructor the compiler would declare itself (see C1) wouldn't be constexpr. It is not constexpr because it doesn't initialize all data members. Note that all of the user-declared ctors in the OP are (implicitly) private.


    class C4 {
        constexpr C4() noexcept = default;
        int* ptr = nullptr;
        int val = 0;
    };
    

    Same as C2.


    class C5 {
        constexpr C5() noexcept : ptr{nullptr}, val{0} = default;
        int* ptr;
        int val;
    };
    

    Gramatically illegal. You cannot default only the function body, you have to default the whole constructor.


    class C6 {
        constexpr C6() noexcept : ptr{nullptr}, val{0} {}
        int* ptr;
        int val;
    };
    

    Same effects as C2, except this is a private ctor and struct C6 would no longer be an aggregate because this ctor is user-provided.


    class C7 {
        constexpr C7() noexcept;
        int* ptr;
        int val;
    };
    constexpr C7::C7() noexcept = default;
    

    Illegal for the same reason as C3.


    class C8 {
        constexpr C8() noexcept;
        int* ptr = nullptr;
        int val = 0;
    };
    constexpr C8::C8() noexcept = default;
    

    Since the ctor is not defaulted at its first declaration, this is a (private) user-provided default ctor. Therefore, same behaviour as C6.


    class C9 {
        constexpr C9() noexcept;
        int* ptr;
        int val;
    };
    constexpr C9::C9() noexcept : ptr{nullptr}, val{0} = default;
    

    Illegal for the same reason as C3.


    class C10 {
        constexpr C10() noexcept;
        int* ptr;
        int val;
    };
    constexpr C10::C10() noexcept : ptr{nullptr}, val{0} {}
    

    Same as C8; constexpr functions are implicitly inline.