Introduction:
The data attributes within a class are initialized through an initializer list. The constructor defines this list by presenting the members to initialize as a series separated by commas, followed by a colon. Below is an example demonstrating the initialization of the x and y attributes of the Point class utilizing the initializer list.
#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(11, 17);
cout<<"x = "<<t1.getX()<<", ";
cout<<"y = "<<t1.getY();
return 0;
}
Output:
x = 11, y = 17
Explanation:
The code demonstrated previously functions as an example of how the syntax of the Initializer list is utilized. Within the provided code snippet, the straightforward process of initializing variables x and y within the constructor is also depicted. Nonetheless, there are specific scenarios where opting for an initializer list over initializing data members inside the constructor becomes essential. These scenarios include:
1) For non-static const data members initialization:
Const data members necessitate the utilization of an Initializer List for initialization. In the given illustration, "t" is an initializer list-initialized constant data member within the Test class. These constant data members are initialized in the initializer list since they do not have memory allocated explicitly for them; instead, they are integrated into the symbol table, making it imperative to initialize them in the initializer list.
Moreover, since it is a parameterized constructor, there is no necessity to invoke the assignment operator, thus eliminating the need for an extra operation.
#include<iostream>
using namespace std;
class Test {
const int t;
public:
Test(int t):t(t) {} //Here initializer is used
int getT() { return t; }
};
int main() {
Test t1(13);
cout<<t1.getT();
return 0;
}
Output:
2) For the reference members' initialization:
Initializer List is necessary for initializing reference members. In the provided example, "t" is an initializer list used to initialize reference members within the Test class.
// initialization of the members of reference data
#include<iostream>
using namespace std;
class Test {
int &t;
public:
Test(int &t):t(t) {} //Use of an initializer list is required.
int getT() { return t; }
};
int main() {
int x = 50;
Test t1(x);
cout<<t1.getT()<<endl;
x = 60;
cout<<t1.getT()<<endl;
return 0;
}
Output:
50
60
3) For member objects that do not have a default constructor to be initialised:
As illustrated in the instance provided, there is no default constructor for class "A", and the object "a" belonging to class "A" serves as a data member within class "B". Initializing "a" using an initializer list is essential in this scenario.
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
};
A::A(int arg) {
i = arg;
cout << " Constructor A is 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 << " Constructor B is called";
}
int main() {
B obj(50);
return 0;
}
Output:
Constructor A is called: Value of i: 50
Constructor B is called
Explanation:
Initializer List is unnecessary when initializing variable "a" with the default constructor. However, it becomes necessary when initializing "a" with the parameterized constructor, especially in scenarios where class A offers both default and parameterized constructors.
4) For base class member initialization:
Just like point 3, solely the Initializer List is permissible for invoking the parameterized constructor of the base class.
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
};
A::A(int arg) {
i = arg;
cout << "Constructor A is 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 << "Constructor B is called";
}
int main() {
B obj(46);
return 0;
}
Output:
Constructor A is called: Value of i: 46
Constructor B is called
5) When a data member's name is the same as a constructor parameter
If the constructor argument name matches the data member name, it is necessary to use either the this pointer or the Initializer List to initialize the data member. In the provided example, both the member name and the parameter name for the constructor A are "i".
#include <iostream>
using namespace std;
class A {
int i;
public:
A(int );
int getI() const { return i; }
};
A::A(int i):i(i) { } // This pointer or the initializer list must be used.
/* The constructor described above can alternatively be written as
A::A(int i) {
this->i = i;
}
*/
int main() {
A a(45);
cout<<a.getI();
return 0;
}
Output:
6) For purposes of performance:
Instead of setting values within the body, it is recommended to initialize all class variables in the Initializer List. Consider the scenario below:
// absence of initializer list
class MyClass {
Type variable;
public:
MyClass(Type a) { //Assume Type is a class that has previously been declared and has the necessary constructors and operators.
variable = a;
}
};
The compiler follows these steps to produce an instance of the MyClass class.
The constructor of the type is initially called for "a".
"variable" is the default construct.
The MyClass constructor invokes the assignment operator of "Type" to perform the assignment.
Variable a;
The processes that the compiler takes while dealing with the Initializer List are as follows:
- First, "a" is passed to the constructor of the type.
- The "Type" class's parameterized constructor is used to initialize a variable(a) . Direct copying of the construct "variable" is accomplished using the arguments in the initializer list .
- Since "a" exits the scope of "Type" , the destructor of "Type" is invoked. This example demonstrates how using assignment inside the constructor body results in three function calls: constructor, destructor , and one call for the addition assignment operator . Additionally, there are just two function calls when using an Initializer List: a copy constructor call and a destructor call .
- Finally, since "a" is no longer within the scope of "Type" , the destructor of "Type" is invoked.
Consider the identical code adjusted to integrate the Initializer List within the constructor of MyClass.
// With Initializer List
class MyClass {
Type variable;
public:
MyClass(Type a):variable(a) { // Assume Type is a class that has previously been declared and has the necessary constructors and operators.
}
};