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.
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.