Skip to main content

When do we use Initializer List in C++?

An initializer list is used to initialize the data members of a class. This list of members to be initialized is specified in the constructor as a comma-separated list, followed by a colon. Here is an example that demonstrates the use of an initializer list to initialize the variables x and y in the Point class.

#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 the 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 code provided serves as an example of the syntax used for an initializer list. While the variables x and y can also be initialized within the constructor, there are specific scenarios where initializing data members inside the constructor is not effective. In these cases, an initializer list must be employed. Here are those situations:

1) For initialization of non-static const data members:

Const data members must be initialized using an initializer list. In the following example, "t" is a const data member of the Test class and is initialized using this method. The reason for initializing a const data member in the initializer list is that no separate memory is allocated for it; instead, it is folded into the symbol table. Therefore, it is necessary to initialize it in the initializer list.

Additionally, since this is a copy constructor, we do not need to call the assignment operator. This approach helps us avoid an extra operation.

#include<iostream> 
using namespace std; 
  
class Test { 
    const int t; 
public: 
    Test(int t):t(t) {}  //Initializer list must be used 
    int getT() { return t; } 
}; 
  
int main() { 
    Test t1(10); 
    cout<<t1.getT(); 
    return 0; 
  
/* OUTPUT: 
   10  
*/

2) For initialization of reference members:

Reference members should be initialized using an initializer list. In the following example, "t" is a reference member of the Test class and is initialized using this initializer list.

// 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 default constructor:

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

#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 an 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 has both default and parameterized constructors, then the Initializer List is not necessary if we want to initialize “a” using the default constructor, but it is necessary 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.

#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; 

5) When the constructor’s parameter name is the same as the 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”.

#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:

// 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 the following steps to create an object of type MyClass
1. Type’s constructor is called first for “a”.
2. The assignment operator of “Type” is called inside the body of the MyClass() constructor to assign
    variable = a; 
3. And then finally destructor of “Type” is called for “a” since it goes out of scope.
Now consider the same code with the MyClass() constructor with the Initializer List
// 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 the compiler:
1. The copy constructor of the “Type” class is called to initialize: variable(a). The arguments in the initializer list are used to copy the construct “variable” directly.
2. 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.

Comments

Popular posts from this blog

Understanding push_back and emplace_back in C++

| Understanding push_back and emplace_back in C++ C++ provides several mechanisms to add elements to its containers, and two often used are push_back and emplace_back . Understanding the difference between these methods can help you write more efficient and expressive code. Let's delve into these concepts with examples to illustrate their usage and benefits.

constexpr in C++

|  Let’s dive into the depths of constexpr in C++! constexpr is short for "constant expression." It was introduced in C++11 and further enhanced in C++14 and C++20. The primary purpose of constexpr is to allow the evaluation of expressions at compile-time, enabling several powerful optimizations. Here’s a detailed breakdown: Purpose of constexpr The idea behind constexpr  is to inform the compiler that the value of a variable or the result of a function can be determined at compile-time. It will be if the expression can be evaluated at compile-time, resulting in performance benefits. It’s beneficial for: - Compile-time constants: Values that don’t change at runtime. - Optimizations: Allowing the compiler to optimize code more effectively. - Template metaprogramming: Enhancing the power of templates. Usage in Variables A constexpr  variable must be initialized with a constant expression.  Here’s an example: constexpr int length = 10; constexpr int width = 5; conste...

Reasons for a C++ Program Crash

C++ programs may crash unexpectedly for various reasons. Here are some typical causes of such crashes: Segmentation Fault A segmentation fault is a major cause of program crashes. It occurs when: Attempting to access a memory location that doesn’t exist. Trying to write to a read-only memory location. Accessing protected memory locations, such as kernel memory. Example: int main() {     char *text;     // Stored in the read-only part of the data segment     text = "ABC";     // Problem: trying to modify read-only memory     *(text + 1) = 'n';     return 0; }   Stack Overflow Stack overflow happens due to non-terminating recursion, which exhausts the stack memory. Example: #include <stdio.h> void functionRecursive(int num)  {     if (num =...