A constructor performs its work in the following order:
It calls base class and member constructors in the order of declaration.
If the class is derived from virtual base classes, it initializes the object's virtual base pointers.
If the class has or inherits virtual functions, it initializes the object's virtual function pointers. Virtual function pointers point to the class's virtual function table to enable correct binding of virtual function calls to code.
It executes any code in its function body.
The following example shows the order in which base class and member constructors are called in the constructor for a derived class. First, the base constructor is called, then the base-class members are initialized in the order in which they appear in the class declaration, and then the derived constructor is called.
#include <iostream>
using namespace std;
class Contained1 {
public:
Contained1() {
cout << "Contained1 constructor." << endl;
}
};
class Contained2 {
public:
Contained2() {
cout << "Contained2 constructor." << endl;
}
};
class Contained3 {
public:
Contained3() {
cout << "Contained3 constructor." << endl;
}
};
class BaseContainer {
public:
BaseContainer() {
cout << "BaseContainer constructor." << endl;
}
private:
Contained1 c1;
Contained2 c2;
};
class DerivedContainer : public BaseContainer {
public:
DerivedContainer() : BaseContainer() {
cout << "DerivedContainer constructor." << endl;
}
private:
Contained3 c3;
};
int main() {
DerivedContainer dc;
int x = 3;
}
Here's the output:
Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.
If a constructor throws an exception, the order of destruction is the reverse of the order of construction:
The code in the body of the constructor function is unwound.
Base class and member objects are destroyed, in the reverse order of declaration.
If the constructor is non-delegating, all fully-constructed base class objects and members are destroyed. However, because the object itself is not fully constructed, the destructor is not run.
Example
#include <iostream>
using namespace std;
class Contained1 {
public:
// constructor is removed
~Contained1() {
cout << "Contained1 destructor." << endl;
}
};
class Contained2 {
public:
// constructor is removed
~Contained2() {
cout << "Contained2 destructor." << endl;
}
};
class Contained3 {
public:
// constructor is removed
~Contained3() {
cout << "Contained3 destructor." << endl;
}
};
class BaseContainer {
public:
// constructor is removed
~BaseContainer() {
cout << "BaseContainer destructor." << endl;
}
private:
Contained1 c1;
Contained2 c2;
};
class DerivedContainer : public BaseContainer {
public:
DerivedContainer() : BaseContainer() {
try
{
throw new exception();
}
catch (...)
{
cout << endl<<"Exception catched." << endl;
}
}
~DerivedContainer() {
cout << "DerivedContainer destructor." << endl;
}
private:
Contained3 c3;
};
int main() {
DerivedContainer dc;
}
the output is:
Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.
Exception catched.
DerivedContainer destructor.
Contained3 destructor.
BaseContainer destructor.
Contained2 destructor.
Contained1 destructor.
Member Lists
We can initialize class members from constructor arguments by using a member initializer list. This method uses direct initialization, which is more efficient than using assignment operators inside the constructor body.
class Box {
public:
Box(int width, int length, int height)
: m_width(width), m_length(length), m_height(height) // member init list
{}
int Volume() {return m_width * m_length * m_height; }
private:
int m_width;
int m_length;
int m_height;
};
Create a Box object
Box b(42, 21, 12);
cout << "The volume is " << b.Volume();
Explicit Constructor
If a class has a constructor with a single parameter, or if all parameters except one have a default value, the parameter type can be implicitly converted to the class type. For example, if the Box class has a constructor like this:
Box(int size): m_width(size), m_length(size), m_height(size){}
It is possible to initialize a Box like this:
Box b = 42;
or pass an int to a function that takes a Box:
class ShippingOrder
{
public:
ShippingOrder(Box b, double postage) : m_box(b), m_postage(postage){}
private:
Box m_box;
double m_postage;
}
//elsewhere...
ShippingOrder so(42, 10.8);
Such conversions can be useful in some cases, but more often they can lead to subtle but serious errors in your code. As a general rule, you should use the explicit keyword on a constructor (and user-defined operators) to prevent this kind of implicit type conversion.
explicit Box(int size): m_width(size), m_length(size), m_height(size){...}
When the constructor is explicit, this line causes a compiler error: ShippingOrder so(42, 10.8).
Default Constructor
Default constructors have no parameters; they follow slightly different rules:
Default constructors are one of the special member functions; if no constructors are declared in a class, the compiler provides a default constructor.
class Box {
Box(int width, int length, int height)
: m_width(width), m_length(length), m_height(height){}
};
int main(){
Box box1{}; // call compiler-generated default ctor
Box box2; // call compiler-generated default ctor
}
When you call a default constructor and try to use parentheses, a warning is issued:
class myclass{};
int main(){
myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?)
}
This is an example of the Most Vexing Parse problem. The example expression can be interpreted either as the declaration of a function or as the invocation of a default constructor, and because C++ parsers favor declarations over other things, the expression is treated as a function declaration. |
---|
If any non-default constructors are declared, the compiler does not provide a default constructor.
class Box {
public:
Box(int width, int length, int height)
: m_width(width), m_length(length), m_height(height){}
private:
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
Box box4; // compiler error C2512: no appropriate default constructor available
}
If a class has no default constructor, an array of objects of that class cannot be constructed by using square-bracket syntax alone. For example, given the previous code block, an array of Boxes cannot be declared like this:
Box boxes[3]; // compiler error C2512: no appropriate default constructor available
However, you can use a set of initializer lists to initialize an array of Boxes:
Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Virtual Functions in Constructors
You should be careful when you call virtual functions in constructors. Because the base class constructor is always invoked before the derived class constructor, the function that's called in the base constructor is the base class version, not the derived class version. In the following example, constructing a DerivedClass causes the BaseClass implementation of print_it() to execute before the DerivedClass constructor causes the DerivedClass implementation of print_it() to execute:
#include <iostream>
using namespace std;
class BaseClass {
public:
BaseClass() {
print_it();
}
virtual void print_it() {
cout << "BaseClass print_it" << endl;
}
};
class DerivedClass : public BaseClass {
public:
DerivedClass() {
print_it();
}
virtual void print_it() {
cout << "Derived Class print_it" << endl;
}
};
int main() {
DerivedClass dc;
}
the output is
BaseClass print_it
Derived Class print_it
Constructors and Composite Classes
Classes that contain class-type members are known as composite classes. When a class-type member of a composite class is created, the constructor of the members are called before the class's own constructor.
#include <iostream>
using namespace std;
#include <iostream>
using namespace std;
class Label {
public:
Label(const string& name, const string& address) { m_name = name; m_address = address; }
string m_name;
string m_address;
};
class Box {
public:
Box(int width, int length, int height)
: m_width(width), m_length(length), m_height(height) {}
private:
int m_width;
int m_length;
int m_height;
};
class StorageBox : public Box {
public:
StorageBox(int width, int length, int height, Label label)
: Box(width, length, height), m_label(label) {}
private:
Label m_label;
};
int main() {
// passing a named Label
Label label1{ "some_name", "some_address" };
StorageBox sb1(1, 2, 3, label1);
// passing a temporary label
StorageBox sb2(3, 4, 5, Label{ "another name", "another address" });
// passing a temporary label as an initializer list
StorageBox sb3(1, 2, 3, { "myname", "myaddress" });
}
Inheriting constructors (C++11)
A derived class can inherit the constructors from a direct base class by using a using declaration as shown in the following example:
#include <iostream>
using namespace std;
class Base
{
public:
Base() { cout << "Base()" << endl; }
Base(const Base& other) { cout << "Base(Base&)" << endl; }
explicit Base(int i) : num(i) { cout << "Base(int)" << endl; }
explicit Base(char c) : letter(c) { cout << "Base(char)" << endl; }
private:
int num{0};
char letter{};
};
class Derived : Base
{
public:
// Inherit all constructors from Base
using Base::Base;
private:
// Can't initialize new member from Base constructors.
int newMember{ 0 };
};
int main(int argc, char argv[])
{
cout << "Derived d1(5) calls: ";
Derived d1(5);
cout << "Derived d1('c') calls: ";
Derived d2('c');
cout << "Derived d3 = d2 calls: ";
Derived d3 = d2;
cout << "Derived d4 calls: ";
Derived d4;
return 0;
}
the output is
Derived d1(5) calls: Base(int)
Derived d1('c') calls: Base(char)
Derived d3 = d2 calls: Base(Base&)
Derived d4 calls: Base()
The using statement brings all constructors from the base class except those that have an identical signature to constructors in the derived class. In general, it is best to use inheriting constructors when the derived class declares no new data members or constructors.
A class template can inherit all the constructors from a type argument if that type specifies a base class:
template< typename T > class Derived : T { using T::T; // declare the constructors from T // ... };
Rules for Declaring Constructors
Default constructors can be called with no arguments. However, you can declare a default constructor with arguments, provided all arguments have defaults.
class Sample
{
public:
Sample() { }
Sample(int arg=0) { }
};
Similarly, copy constructors must accept a single argument of reference to the same class type. More arguments can be supplied, provided all subsequent arguments have defaults.
class Sample
{
public:
//copy ctor
Sample(const sample& rhs) { }
// copy ctor
Sample(const sample& rhs, int value=0) { }
};
You can use a constructor to initialize a const or volatile object, but the constructor itself cannot be declared as const or volatile. The only legal storage class for a constructor is inline; use of any other storage-class modifier, including the __declspec keyword, with a constructor causes a compiler error.
Comments