Open In App

When do we use Initializer List in C++?

Improve
Improve
Improve
Like Article
Like
Save Article
Save
Share
Report issue
Report

Initializer List is used in initializing the data members of a class. The list of members to be initialized is indicated with constructor as a comma-separated list followed by a colon. Following is an example that uses the initializer list to initialize x and y of Point class.

Example

C++




#include <iostream>
using namespace std;
 
class Point {
private:
    int x;
    int y;
 
public:
    Point(int i = 0, int j = 0): x(i), y(j) {}
    /*  The above use of Initializer list is optional as the
        constructor can also be written as:
        Point(int i = 0, int j = 0) {
            x = i;
            y = j;
        }
    */
 
    int getX() const { return x; }
    int getY() const { return y; }
};
 
int main()
{
    Point t1(10, 15);
    cout << "x = " << t1.getX() << ", ";
    cout << "y = " << t1.getY();
    return 0;
}


Output

x = 10, y = 15

The above code is just an example for syntax of the Initializer list. In the above code, x and y can also be easily initialed inside the constructor. But there are situations where initialization of data members inside constructor doesn’t work and Initializer List must be used. The following are such cases:

1. For Initialization of Non-Static const Data Members

const data members must be initialized using Initializer List. In the following example, “t” is a const data member of Test class and is initialized using Initializer List. Reason for initializing the const data member in the initializer list is because no memory is allocated separately for const data member, it is folded in the symbol table due to which we need to initialize it in the initializer list. 

Also, it is a Parameterized constructor and we don’t need to call the assignment operator which means we are avoiding one extra operation.

Example

C++




// C++ progmram to demonstrate the use of
// initializer list to initialize the const
// data member
#include<iostream>
using namespace std;
 
class Test {
    const int t;
public:
  //Initializer list must be used
    Test(int t):t(t) {}
    int getT() { return t; }
};
 
int main() {
    Test t1(10);
    cout<<t1.getT();
    return 0;
}


Output

10

2. For Initialization of Reference Members

Reference members must be initialized using the Initializer List. In the following example, “t” is a reference member of the Test class and is initialized using the Initializer List.

Example

C++




// Initialization of reference data members
#include<iostream>
using namespace std;
 
class Test {
    int &t;
public:
    Test(int &t):t(t) {}  //Initializer list must be used
    int getT() { return t; }
};
 
int main() {
    int x = 20;
    Test t1(x);
    cout<<t1.getT()<<endl;
    x = 30;
    cout<<t1.getT()<<endl;
    return 0;
}


Output

20
30

3. For Initialization of Member Objects that do not have a Default Constructor

In the following example, an object “a” of class “A” is a data member of class “B”, and “A” doesn’t have a default constructor. Initializer List must be used to initialize “a”.

Example

C++




// C++ progmam to initialize a member object without default
// constructor
#include <iostream>
using namespace std;
 
class A {
    int i;
 
public:
    A(int);
};
 
A::A(int arg)
{
    i = arg;
    cout << "A's Constructor called: Value of i: " << i
         << endl;
}
 
// Class B contains object of A
class B {
    A a;
 
public:
    B(int);
};
 
B::B(int x) : a(x)
{ // Initializer list must be used
    cout << "B's Constructor called";
}
 
int main()
{
    B obj(10);
    return 0;
}


Output

A's Constructor called: Value of i: 10
B's Constructor called

If class A had both default and parameterized constructors, then Initializer List is not a must if we want to initialize “a” using the default constructor, but it is must to initialize “a” using the parameterized constructor. 

4. For Initialization of Base Class Members

Like point 3, the parameterized constructor of the base class can only be called using the Initializer List.

Example

C++




#include <iostream>
using namespace std;
 
class A {
    int i;
public:
    A(int );
};
 
A::A(int arg) {
    i = arg;
    cout << "A's Constructor called: Value of i: " << i << endl;
}
 
// Class B is derived from A
class B: A {
public:
    B(int );
};
 
B::B(int x):A(x) { //Initializer list must be used
    cout << "B's Constructor called";
}
 
int main() {
    B obj(10);
    return 0;
}


Output

A's Constructor called: Value of i: 10
B's Constructor called

5. When the Constructor’s Parameter Name is the Same as Data Member

If the constructor’s parameter name is the same as the data member name then the data member must be initialized either using this pointer or Initializer List. In the following example, both the member name and parameter name for A() is “i”.

Example

C++




#include <iostream>
using namespace std;
 
class A {
    int i;
 
public:
    A(int);
    int getI() const { return i; }
};
 
A::A(int i) : i(i)
{
} // Either Initializer list or this pointer must be used
/* The above constructor can also be written as
A::A(int i) {
    this->i = i;
}
*/
 
int main()
{
    A a(10);
    cout << a.getI();
    return 0;
}


Output

10

6. For Performance Reasons

It is better to initialize all class variables in the Initializer List instead of assigning values inside the body. Consider the following example:

Example

C++




// Without Initializer List
class MyClass {
    Type variable;
public:
    MyClass(Type a) {  // Assume that Type is an already
                     // declared class and it has appropriate
                     // constructors and operators
      variable = a;
    }
};


Here compiler follows following steps to create an object of type MyClass 

1. Type’s constructor is called first for “a”. 

2. Default construct “variable”

3. The assignment operator of “Type” is called inside body of MyClass() constructor to assign 

variable = a;

4. And then finally destructor of “Type” is called for “a” since it goes out of scope.

Now consider the same code with MyClass() constructor with Initializer List

C++




// With Initializer List
class MyClass {
    Type variable;
public:
    MyClass(Type a):variable(a) {   // Assume that Type is an already
                     // declared class and it has appropriate
                     // constructors and operators
    }
};


With the Initializer List, the following steps are followed by compiler: 

1. Type’s constructor is called first for “a”. 
2. Parameterized constructor of “Type” class is called to initialize: variable(a). The arguments in the initializer list are used to copy construct “variable” directly. 
3. The destructor of “Type” is called for “a” since it goes out of scope.

As we can see from this example if we use assignment inside constructor body there are three function calls: constructor + destructor + one addition assignment operator call. And if we use Initializer List there are only two function calls: copy constructor + destructor call. See this post for a running example on this point.

This assignment penalty will be much more in “real” applications where there will be many such variables. Thanks to ptr for adding this point. 

Parameter vs Uniform Initialization in C++

It is better to use an initialization list with uniform initialization {} rather than parameter initialization () to avoid the issue of narrowing conversions and unexpected behavior. It provides stricter type-checking during initialization and prevents potential narrowing conversions

Code using parameter initialization () 

C++




#include <iostream>
 
class Base {
    char x;
 
public:
    Base(char a)
        : x{ a }
    {
    }
 
    void print() { std::cout << static_cast<int>(x); }
};
 
int main()
{
    Base b{ 300 }; // Using uniform initialization with {}
    b.print();
    return 0;
}


Output

44

In the above code, the value 300 is out of the valid range for char, which may lead to undefined behavior and potentially incorrect results. The compiler might generate a warning or error for this situation, depending on the compilation settings.

Code using uniform initialization {}

By using uniform initialization with {} and initializing x with the provided value a, the compiler will perform stricter type-checking and issue a warning or error during compilation, indicating the narrowing conversion from int to char.
Here is code with uniform initialization {} , which results in a warning and thus better to use

C++




#include <iostream>
 
class Base {
    char x;
 
public:
    Base(char a)
        : x{ a }
    {
    }
 
    void print() { std::cout << static_cast<int>(x); }
};
 
int main()
{
    Base b{ 300 }; // Using uniform initialization with {}
    b.print();
    return 0;
}


main.cpp: In function ‘int main()’:
main.cpp:17:17: error: narrowing conversion of ‘300’ from ‘int’ to ‘char’ [-Wnarrowing]
   17 |     Base b{ 300 }; // Using uniform initialization with {}
      |                 ^



Last Updated : 15 Nov, 2023
Like Article
Save Article
Previous
Next
Share your thoughts in the comments
Similar Reads