Member Initializer List in C++ (ENG)

Member Initializer List

There isn't much talk about this member initializer list really. We only use initializer list to initialize a new class object. You may see the member initializer list after a function definition of any kind of constructor, syntax with a colon character : . For example, consider the following:

class MyClass{
	int x, y, z;
public:
	// Most commonly used and seen
	MyClass(int _x, int _y, int _z) : x(_x), y(_y), z(_z) {}

	// Brace-enclosed initializer list semantics after C++11
	MyClass(int _x, int _y, int _z) : x{_x}, y{_y}, z{_z} {}
};

But we have to following some rules here to order:

  1. Reference members cannot be bound to temporaries in a member initializer list.
    class MyClass{
    	const int& ref;
    	MyClass() : ref(10) {} // This would cause an error
    };
    
  2. Initialization do follow some orders:
    • The compiler will initialize the object members in the order they are declared in the class, not the order they appear in the initializer list.
    class MyClass {
        int x, y, z;
    public:
    	// x is still initialized before y and z
        MyClass(int _x, int _y, int _z) : z(_z), y(_y), x(_x) {} 
    };
    
    • So, when the x is initialized after y or z, or we say y is initialized after z, shit happens:
    class MyClass {
        int x, y, z;
    public:
    	// x is initialized using the uninitialized y
    	MyClass(int _x, int _y, int _z) : x(y), y(_y), z(_z) {}
    };
    

Curly Brace vs. Parenthesis

So, why are you recommended to use {} to initialize an object even though we have ()? Because they have different semantics. An exact example is how they are used in STL container.

std::vector<int> v(100, 1);  // Initializes a vector containing 100 items of value 1
std::vector<int> v{100, 1};  // Initializes a vector containing 2 items: 100 and 1

int i(3.14);  // Okay, i will be assigned the value 3 (narrowing conversion)
int i{3.14};  // Error, the assigned value must be type-specific (no narrowing)

Another great thing with curly braces {} is automatically initial everything to zero for basic data types. For example:

int a{}; // a is initialized to 0
void* ptr{}; // ptr is initialized to nullptr

class MyClass {
public:
    int a;
    void* ptr;
    MyClass() = default;
};

MyClass obj{}; // All members are initialized to 0 (pointer to nullptr)

But note that if you have a user-defined constructor like this, it can cause undefined behavior:

class MyClass {
public:
    int a;
    void* ptr;
    MyClass() {} // Would cause undefined behavior
};

MyClass obj{}; // Not all members are initialized

std::initializer_list

Use initializer list for copy avoidance purposes.

#include <iostream>

class MyClass {
public:
    MyClass(int x, int y) {
        std::cout << "Regular constructor called with " << x << " and " << y << std::endl;
    }

    MyClass(std::initializer_list<int> list) {
        std::cout << "Initializer list constructor called with ";
        for (auto elem : list) {
            std::cout << elem << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    MyClass obj1(100, 200);     // Calls the regular constructor
    MyClass obj2{100, 200};     // Calls the initializer list constructor
	// MyClass obj3{100.5, 200.5}; // No narrowing conversion
    return 0;
}