We get a mess, the mess get worse, so it’s time to switch to a new language. – Nicolai Josuttis
- Initialization of C++ is Bonkers
Initialization of C++ is Bonkers
Intializer may be one the the following
()
=
{}
Depending on the context, it may invoke:
- Default initialization
- Copy initialization
- Value initialization
- Aggregate initialization
- Direct initialization
- List initialization
Different C++ standard may have different rules
Default initialization
This is the initialization performed when a variable is constructed with no initializer.
Syntax
T object;
new T;
new T(); // until C++03
Examples & Traps
int i;
int j = i; // UB
However, unsigned char
is an exception
unsigned char i;
unsigned char j = i; // no undefined
For struct
in C/C++, and class
in C++, default initalization does not initialize anything by default
struct S {
int i;
};
S s; // calls implicit default ctor
int j = s.i; // UB
class C {
int i;
public:
C() {
}
int getI() {
return i; }
};
C c;
int j = c.getI(); // UB
If T is a const-qualified type, it must be a class type with a user-provided default constructor.
A constructor is user-provided if it is user-declared and not explicitly defaulted on its first declaration.
const int i; // Error
struct S {
int i;
};
const S s; // Error
struct X {
X() = default; // user-declared, but not user-provided
int i;
};
const X x; // Error
struct Y {
Y();
int i;
};
Y::Y() = default;
const Y y; // OK
Summary
- built-in types: uninitialized, UB on access
- class types: default constructor
To avoid UB, use Direct Member Initialization whenever possible
struct S {
int i = 0;
W w = {
};
};
Copy initialization
Initializes an object from another object
Syntax
T object = other;
T object = {
other}; // until C++11
T array[N] = {
other};
f(other)
return other;
throw object;
catch (T object)
- initializer starts with
=
, or - Passing arguments by value, or
- Returning by value, or
- Throwing/Catching exceptions by value
Summary
-
Copy initialization is never an assignment
-
If type don’t match, copy initialization performs a conversion sequence, but explicit ctors are not participate
struct S { explicit S(int); }; void foo(const S &); foo(42); // error S s1(42); S s2{ 42}; S s3 = 42; // error S s4 = { 42}; // error
-
The implicit conversion must produce T directly from the initializer.
struct S { S(std::string); }; S s1 = "hello" // Error, no conversion from const char [6] to std::string S s2 = ("hello"); // ditto, only const char * to std::string S s3("hello"); // OK S s4 = { "hello"}; // OK, different rules apply S s5{ "hello"}; // OK
Aggregate intialization
Initializes an aggregate from braced-init-list
Syntax
T object = {
arg1, arg2, ...};
T object {
arg1, arg2, ...}; // since C++11
- An array, or
- A class (struct/union), that has
- C++98
- no user-declared ctors
- no private or protected non-static data members
- no base class
- no virtual functions
- C++11/14
- no user-provided ctors
- no private or protected non-static data memebers
- no base class
- no virtual functions
- no default member initializers
- C++17
- no user-provided, explicit, or inherited ctors
- no private or protected non-static data members
- no virtual, private, or prtected base class
- no virtual functions
- C++98
Examples & Traps
Aggregate definition got relaxed standard after standard
struct Data {
std::string name;
double value;
};
struct DV: Data {
// before C++17, this is a class with default ctor
bool used; // since C++17, this is an aggregate
void print() const;
};
DV u; // value and used are undefined
DV v{
}; // value and used with 0 and false
DV v{
{
"hello", 3}, true};
DV v{
"hello", 3, true};
Aggregate initialization copy init its member
struct S {
explicit S(int);
};
struct A {
S s1, s2;
};
S s = {
3, 4}; // Error, because S(int) is explicit
struct S {
explicit S(int);
S(double);
};
S s = {
3, 4}; // calls S(double)
Aggregate initialzation zero init its remaining elements
struct S {
int i, j;
};
S s = {
1}; // s.i = 1, s.j = 0
int a[100] = {
}; // all zeros
Brace elision
struct S {
int i, j;
};
struct A {
S s;
int k;
};
A a = {
1, 2}; // A a = {
{1, 2}, 0}
struct S {
S() = delete; // user-declared, but not user-provided, so since c++11, it is an aggregate
};
S s1; // error
// WTF of the month -- on Nicolai Josuttis twitter
// Or a nice way to force to initialize an aggregate
S s2{
}; // ok since c++11, fixed in C++20
struct S {
int i = 0;
S(int) = delete;
};
S s1(3); // error
S s2{
3}; // ok since c++11, fixed in C++20
std::array
is an aggregate, it can be ridiculous
- Without intializaton elements might have undefined values
- Nested intializations need an additional pair of braces
std::array<int, 10> a; // values are undefined
std::array<int, 10> b{
}; // values are initialized with 0
std