Initializers

An initializer specifies the initial value of a variable. You can initialize variables in these contexts:

  • In the definition of a variable:

    int i = 3;
    Point p1{ 1, 2 };
    
  • As one of the parameters of a function:

    set_point(Point{ 5, 6 });
    
  • As the return value of a function:

    Point get_new_point(int x, int y) { return { x, y }; }
    Point get_new_point(int x, int y) { return Point{ x, y }; }
    

Initializers may take these forms:

  • An expression (or a comma-separated list of expressions) in parentheses:

    Point p1(1, 2);
    
  • An equals sign followed by an expression:

    string s = "hello";
    
  • A braced initializer list. The list may be empty or may consist of a set of lists:

    struct Point{
        int x;
        int y;
    };
    class PointConsumer{
    public:
        void set_point(Point p){};
        void set_points(initializer_list<Point> my_list){};
    };
    int main() {
        PointConsumer pc{};
        pc.set_point({});
        pc.set_point({ 3, 4 });
        pc.set_points({ { 3, 4 }, { 5, 6 } });
    }
    

Kinds of Initialization

There are several kinds of initialization, which may occur at different points in program execution. Different kinds of initialization are not mutually exclusive—for example, list initialization can trigger value initialization and in other circumstances, it can trigger aggregate initialization.

Zero Initialization

Zero initialization is the setting of a variable to a zero value implicitly converted to the type:

  • Numeric variables are initialized to 0 (or 0.0, or 0.0000000000, etc.).

  • Char variables are initialized to ‘\0’.

  • Pointers are initialized to nullptr.

  • Arrays, POD classes, structs, and unions have their members initialized to a zero value.

Zero initialization is performed at different times:

  • At program startup, for all named variables that have static duration. These variables may later be initialized again.

  • During value initialization, for scalar types and POD class types that are initialized by using empty braces.

  • For arrays that have only a subset of their members initialized.

Here are some examples of zero initialization:

struct my_struct{
    int i;
    char c;
};

int i0;              // zero-initialized to 0
int main() {
    static float f1;  // zero-initialized to 0.000000000
    double d{};     // zero-initialized to 0.00000000000000000
    int* ptr{};     // initialized to nullptr
    char s_array[3]{'a', 'b'};  // the third char is initialized to '\0'
    int int_array[5] = { 8, 9, 10 };  // the fourth and fifth ints are initialized to 0
    my_struct a_struct{};   // i = 0, c = '\0'
}

Default Initialization

Default initialization for classes, structs, and unions is initialization that uses a default constructor. The default constructor can be called by not including an initialization expression or by using the new keyword:

MyClass mc1;
MyClass* mc3 = new MyClass;

If the class, struct, or union does not have a default constructor, the compiler emits an error.

Scalar variables are default-initialized when they are defined without an initialization expression. They have indeterminate values.

int i1;
float f;
char c;

Arrays are default-initialized when they are defined without an initialization expression. When an array is default-initialized, its members are default-initialized and have indeterminate values:

int int_arr[3];

If the array members do not have a default constructor, the compiler emits an error.

Default Initialization of Constant Variables

Constant variables must be declared together with an initializer. If they are scalar types, they cause a compiler error, and if they are class types that have a default constructor, they cause a warning:

class MyClass{};
int main() {
    //const int i2;   // compiler error C2734: const object must be initialized if not extern
    //const char c2;  // same error
    const MyClass mc1; // compiler error C4269: 'const automatic data initialized with compiler generated default constructor produces unreliable results
}

Default Initialization of Static Variables

Static variables that are declared without an initializer are initialized to 0 (implicitly converted to the type):

class MyClass {   
private:
    int m_int;
    char m_char;
};

int main() {
    static int int1;       // 0
    static char char1;     // '\0'
    static bool bool1;   // false
    static MyClass mc1;     // {0, '\0'}
}

For more information about initialization of global static objects, see Additional Startup Considerations.

Value Initialization

Value initialization occurs in these cases:

  • A named value is initialized using empty brace initialization.

  • An anonymous temporary object is initialized by using empty parentheses or braces.

  • An object is initialized by using the new keyword plus empty parentheses or braces.

Value initialization does this:

  • For classes that have at least one public constructor, the default constructor is called.

  • For non-union classes that don't have declared constructors, the object is zero-initialized and the default constructor is called.

  • For arrays, every element is value-initialized.

  • In all other cases, the variable is zero-initialized.

class BaseClass {  
private:
    int m_int;
};

int main() {
    BaseClass bc{};     // class is initialized
    BaseClass*  bc2 = new BaseClass();  // class is initialized, m_int value is 0
    int int_arr[3]{};  // value of all members is 0
    int a{};     // value of a is 0
    double b{};  // value of b is 0.00000000000000000
}

Copy Initialization

Copy initialization is the initialization of one object by using a different object. It occurs in these cases:

  • A variable is initialized by using an equals sign.

  • An argument is passed to a function.

  • An object is returned from a function.

  • An exception is thrown or caught.

  • A non-static data member is initialized by using an equals sign.

  • Class, struct, and union members are initialized by copy initialization during aggregate initialization. See Aggregate initialization for examples.

This code shows examples of copy initialization:

#include <iostream>
using namespace std;

class MyClass{
public:
    MyClass(int myInt) {}
    void set_int(int myInt) { m_int = myInt; }
    int get_int() const { return m_int; }
private:
    int m_int = 7; // copy initialization of m_int

};
class MyException : public exception{};
int main() {
    int i = 5;              // copy initialization of i
    MyClass mc1{ i };
    MyClass mc2 = mc1;      // copy initialization of mc2 from mc1
    MyClass mc1.set_int(i);    // copy initialization of parameter from i
    int i2 = mc2.get_int(); // copy initialization of i2 from return value of get_int()

    try{
        throw MyException();    
    }
    catch (MyException ex){ // copy initialization of ex
        cout << ex.what();  
    }
}

Copy initialization cannot invoke explicit constructors:

vector<int> v = 10; // the constructor is explicit; compiler error C2440: cannot convert from 'int' to 'std::vector<int,std::allocator<_Ty>>'
regex r = "a.*b"; // the constructor is explicit; same error
shared_ptr<int> sp = new int(1729); // the constructor is explicit; same error

In some cases, if the copy constructor of the class is deleted or inaccessible, copy initialization causes a compiler error. For more information, see Explicit Initialization.

Direct Initialization

Direct initialization uses (non-empty) braces or parentheses. Unlike copy initialization, it can invoke explicit constructors. It occurs in these cases:

  • A variable is initialized by using non-empty braces or parentheses.

  • A variable is initialized by using the new keyword plus non-empty braces or parentheses.

  • A variable is initialized by using static_cast.

  • In a constructor, base classes and non-static members are initialized by using an initializer list.

  • In the copy of a captured variable in a lambda expression.

Here are examples of direct initialization:

class BaseClass{
public:
    BaseClass(int n) :m_int(n){} // m_int is direct initialized
private:
    int m_int;
};

class DerivedClass : public BaseClass{
public:
    // BaseClass and m_char are direct initialized
    DerivedClass(int n, char c) : BaseClass(n), m_char(c) {}
private:
    char m_char;
};
int main(){
    BaseClass bc1(5);
    DerivedClass dc1{ 1, 'c' };
    BaseClass* bc2 = new BaseClass(7);
    BaseClass bc3 = static_cast<BaseClass>(dc1);

    int a = 1;
    function<int()> func = [a](){  return a + 1; }; // a is direct initialized
    int n = func();
}

List Initialization

List initialization occurs when a variable is initialized by using a braced initializer list. Braced initializer lists can be used in these cases:

  • A variable is initialized.

  • A class is initialized by using the new keyword.

  • An object is returned from a function.

  • An argument passed to a function.

  • One of the arguments in a direct initialization.

  • In a non-static data member initializer.

  • In a constructor initializer list.

Examples of list initialization:

class MyClass {
public:
    MyClass(int myInt, char myChar) {}  
private:
    int m_int[]{ 3 };
    char m_char;
};
class MyClassConsumer{
public:
    void set_class(MyClass c) {}
    MyClass get_class() { return MyClass{ 0, '\0' }; }
};
struct MyStruct{
    int my_int;
    char my_char;
    MyClass my_class;
};
int main() {
    MyClass mc1{ 1, 'a' };
    MyClass* mc2 = new MyClass{ 2, 'b' };
    MyClass mc3 = { 3, 'c' };

    MyClassConsumer mcc;
    mcc.set_class(MyClass{ 3, 'c' });
    mcc.set_class({ 4, 'd' });

    MyStruct ms1{ 1, 'a', { 2, 'b' } };
}

Aggregate Initialization

Aggregate initialization is a form of list initialization for arrays or class types (often structs or unions) that have:

  • No private or protected members.

  • No user-provided constructors, except for explicitly defaulted or deleted constructors.

  • No base classes.

  • No virtual member functions.

  • No brace-or-equal initializers for non-static members.

Aggregate initializers consist of a braced initialization list, with or without an equals sign:

#include <iostream>
using namespace std;

struct MyAggregate{
    int myInt;
    char myChar;
};

int main() {
    MyAggregate agg1{ 1, 'c' };

    cout << "agg1: " << agg1.myChar << ": " << agg1.myInt << endl;
    cout << "agg2: " << agg2.myChar << ": " << agg2.myInt << endl;

    int myArr1[]{ 1, 2, 3, 4 };
    int myArr2[3] = { 5, 6, 7 };
    int myArr3[5] = { 8, 9, 10 };

    cout << "myArr1: ";
    for (int i : myArr1){
        cout << i << " ";
    }
    cout << endl;
    
    cout << "myArr3: ";
    for (auto const &i : myArr3) {
        cout << i << " ";
    }
    cout << endl;
}

Here's the output:

agg1: c: 1
agg2: d: 2
myArr1: 1 2 3 4
myArr3: 8 9 10 0 0

Important

Array members that declared but not explicitly initialized during aggregate initialization are zero-initialized, as in myArr3.

Initializing Unions and Structs

If a union does not have a constructor, you can initialize it by using a value (or by using another instance of a union). The value is used to initialize the first non-static field. This is different from struct initialization, in which the first value in the initializer is used to initialize the first field, the second to initialize the second field, and so on. Compare the initialization of unions and structs in this example:

struct MyStruct {
    int myInt;
    char myChar;
};
union MyUnion {
    int my_int;
    char my_char;
    bool my_bool;
    MyStruct my_struct;
};

int main() {  
    MyUnion mu1{ 'a' };  // my_int = 97, my_char = 'a', my_bool = true, {myInt = 97, myChar = '\0'}
    MyUnion mu2{ 1 };   // my_int = 1, my_char = 'x1', my_bool = true, {myInt = 1, myChar = '\0'}
    MyUnion mu3{};      // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
    MyUnion mu4 = mu3;  // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
    //MyUnion mu5{ 1, 'a', true };  // compiler error: C2078: too many initializers
    //MyUnion mu6 = 'a';            // compiler error: C2440: cannot convert from 'char' to 'MyUnion'
    //MyUnion mu7 = 1;              // compiler error: C2440: cannot convert from 'int' to 'MyUnion'

    MyStruct ms1{ 'a' };            // myInt = 97, myChar = '\0'
    MyStruct ms2{ 1 };              // myInt = 1, myChar = '\0'
    MyStruct ms3{};                 // myInt = 0, myChar = '\0'
    MyStruct ms4{1, 'a'};           // myInt = 1, myChar = 'a'
    MyStruct ms5 = { 2, 'b' };      // myInt = 2, myChar = 'b'
}

Initializing Aggregates That Contain Aggregates

Aggregate types can contain other aggregate types—for example, arrays of arrays, arrays of structs, and so on. These types are initialized by using nested sets of braces:

struct MyStruct {
    int myInt;
    char myChar;
};
int main() {
    int intArr1[2][2]{{ 1, 2 }, { 3, 4 }};
    int intArr3[2][2] = {1, 2, 3, 4};  
    MyStruct structArr[]{ { 1, 'a' }, { 2, 'b' }, {3, 'c'} };
}

Reference Initialization

For information about reference initialization, see Initializing References.

Initialization of External Variables

Declarations of automatic, register, static, and external variables can contain initializers. However, declarations of external variables can contain initializers only if the variables are not declared as extern. For more information, see External.

See Also

Reference

Declarators