What is the list of constructor initializers?
To answer this question, first we must clarify the concept of " life cycle " of a data.
Life cycle.
A data exists since it is instantiated until it goes beyond the scope in which it Instanced, for example:
int main()
{
int i = 0; // <--- El dato 'i' nace en este punto.
return i; // <--- El dato 'i' muere en este punto.
}
In the previous code, the variable of type int
called i
begins its life cycle when it is defined ( int i = 0;
) and when it leaves the scope in which it was defined (the function main
) ends its Lifecycle. Let's see another example:
int main()
{
int i = 0; // <--- El dato 'i' nace en este punto.
{
char c = 1; // <--- El dato 'c' nace en este punto.
} // <--- El dato 'c' muere en este punto.
float f = 2.f; // <--- El dato 'f' nace en este punto.
return i; // <--- Los datos 'i' y 'f' mueren en este punto.
}
We see that the data is born when they are defined and die when they leave the scope in which they were defined, in the previous example the function main
has a sub-scope in which it defines a variable of type char
called c
that ends its life cycle before the data f
of type float
is born because its scope ends just before defining f
.
This sub-scope is what happens in the loops for
:
int main()
{
int a = 0; // <--- El dato 'a' nace en este punto.
// v--- El dato 'i' nace en este punto.
for (int i = 0; i < 100; ++i)
{
a += i;
} // <--- El dato 'i' muere en este punto.
return 0; // <--- El dato 'a' muere en este punto.
}
When a piece of information is part of another object, its life cycle is the same as that of the object of which it is part:
struct S { int i; char c; float f; };
int main()
{
int i = 0; // <--- El dato 'i' nace en este punto.
S s; /* <--- El dato 's' nace en este punto, esto implica que...
... s.i, s.c, s.f nacen en el mismo punto. */
return 0; /* <--- Los datos 'i' y 's' mueren en este punto...
... esto implica que s.i, s.c, s.f tambien mueren aqui */
}
We will look in more detail at the data that is part of other objects, since their life cycle begins when creating the object of which they are part, when we enter the body of the constructor these objects already exist ... of not be like that, we could not use them!:
struct S {
int i; char c; float f;
S() { // Constructor de S
i = 1; // El dato 'i' ya existia antes de entrar en S::S()
c = 'c'; // El dato 'c' ya existia antes de entrar en S::S()
f = 0xf; // El dato 'f' ya existia antes de entrar en S::S()
}
};
If the information already existed in the body of the constructor, when did the life cycle begin ?, we can imagine that they have been born between the definition of the constructor and the beginning of the scope of the body of the constructor. Builder:
struct S {
int i; char c; float f;
S() /* Los datos 'i', 'c' y 'f' nacen en este punto. */ {
i = 1;
c = 'c';
f = 0xf;
}
};
Once the life cycle of the sub-objects of an object has been clarified, we can address the question ...
What is the list of constructor initializers?
Sometimes you need to give value to sub-objects of a type at the moment they start their life cycle, this is known as initialization. There are certain types of data that need to be initialized, such as constants or references, so the following code does not compile:
struct S {
const int entero_constante;
int &referencia_a_entero;
S()
{
entero_constante = 1; // Error!
referencia_a_entero = entero_constante; // Error!
}
};
Since S::entero_constante
is a qualified data as constant, it is not possible to modify its value when its life cycle has already begun. Since in C ++ you can not change the object to which a reference is referencing you need to assign value to the references at the time they start their life cycle, so the way we use S::referencia_a_entero
is incorrect 1 ; to solve this problem we must use the list of constructor initializers:
int otro_entero = 1;
struct S {
const int entero_constante;
int &referencia_a_entero;
S() :
entero_constante{1},
referencia_a_entero{otro_entero}
{
}
};
The list of constructor initializers also serves to call the constructor of sub-objects that lack the default constructor:
struct Punto {
int x, y;
// Punto carece de constructor por defecto!
Punto(int a, int b) :
x{a},
y{b}
{}
};
struct Jugador {
std::string nombre;
Punto posicion;
};
int main()
{
Jugador jugador1, jugador2; /* Error! no podemos crear instancias de
Jugador porque Jugador::posición carece
de constructor por defecto y por ello no
se sabe como construir el objeto */
std::cin >> jugador1.nombre >> jugador2.nombre;
return 0;
}
Easy to solve:
struct Punto {
int x, y;
Punto(int a, int b) :
x{a},
y{b}
{}
};
struct Jugador {
std::string nombre;
Punto posicion;
Jugador() :
posicion{0, 0} // Llamada al constructor de Punto
{}
};
int main()
{
Jugador jugador1, jugador2; /* Correcto! ambas instancias de Jugador
tienen el sub-objeto posicion inicializado */
std::cin >> jugador1.nombre >> jugador2.nombre;
return 0;
}
The list of constructor initializers is also used to call the constructor of the base class (s):
struct Punto {
int x, y;
// Punto carece de constructor por defecto!
Punto(int a, int b) :
x{a},
y{b}
{}
};
struct Punto3D : public Punto {
int z;
Punto3D() :
Punto{0, 0}, // Llamada al constructor de Punto
z{0}
{}
};
And (finally) it is also used to delegate the construction to another constructor:
struct Punto {
int x, y;
// Constructor con parametros
Punto(int a, int b) :
x{a},
y{b}
{}
// Constructor por defecto, delegando en el constructor con parametros
Punto() : Punto{0, 0}
{}
};
Summary.
The list of constructor initializers is used for:
- Initialize the sub-objects of the type before they begin their life cycle.
- Call the constructor of the sub-objects of the type.
- Call the constructor of the type (s) base (s) of the type.
- Delegate the construction to other constructors.
1 And because we try to reference a constant data in a non-constant way.