Start of an Object's Lifetime:
In C++, whenever an object of a class is created, its constructor is called. But that's not all--its parent class constructor is called, as are the constructors for all objects that belong to the class. By default, the constructors invoked are the default ("no-argument") constructors. Moreover, all of these constructors are called before the class's own constructor is called.
Example:
include
class Foo
{
public:
Foo() { std::cout << "Foo's constructor" << std::endl; }
};
class Bar : public Foo
{
public:
Bar() { std::cout << "Bar's constructor" << std::endl; }
};
int main()
{
// a lovely elephant ;)
Bar bar;
}
The object bar is constructed in two stages: first, the Foo constructor is invoked and then the Bar constructor is invoked. The output of the above program will be to indicate that Foo's constructor is called first, followed by Bar's constructor.
Why do this? There are a few reasons. First, each class should need to initialize things that belong to it, not things that belong to other classes. So a child class should hand off the work of constructing the portion of it that belongs to the parent class. Second, the child class may depend on these fields when initializing its own fields; therefore, the constructor needs to be called before the child class's constructor runs. In addition, all of the objects that belong to the class should be initialized so that the constructor can use them if it needs to.
But what if you have a parent class that needs to take arguments to its constructor? This is where initialization lists come into play. An initialization list immediately follows the constructor's signature, separated by a colon:
class Foo : public parent_class
{
Foo() : parent_class( "arg" ) // sample initialization list
{
// you must include a body, even if it's merely empty
}
};
Note that to call a particular parent class constructor, you just need to use the name of the class (it's as though you're making a function call to the constructor).
For instance, in our above example, if Foo's constructor took an integer as an argument, we could do this:
include
class Foo
{
public:
Foo( int x )
{
std::cout << "Foo's constructor "
<< called with "
<< x
<< std::endl;
}
};
class Bar
{
public:
Bar() : Foo( 10 ) {
std::cout << "Bar's constructor" << std::endl;
}
};
int main()
{
Bar stool;
}
Using Initialization Lists to Initialize Fields
In addition to letting you pick which constructor of the parent class gets called, the initialization list also lets you specify which constructor gets called for the objects that are fields of the class. For instance, if you have a string inside your class:
class Qux
{
public:
Qux() : _foo( "initialize foo to this!" ) { }
string
private:
std::string _foo;
};
Here, the constructor is invoked by giving the name of the object to be constructed rather than the name of the class (as in the case of using initialization lists to call the parent class's constructor).
If you have multiple fields of a class, then the names of the objects being initialized should appear in the order they are declared in the class (and after any parent class constructor call):
class Baz
{
public:
Baz() : _foo( "initialize foo first" ), _bar( "then bar" ) { }
private:
std::string _foo;
std::string _bar;
};