The constructor is a member function which is called after memory has been allocated for a particular instance of a class. Its purpose is to initialize the object so that it is in a correct state. We will look at two particular instances:
Consider the following class which prints a comment when the constructor is called:
#include <iostream> using namespace std; class Box { private: int N; public: Box( int = 10 ); }; Box::Box( int n ):N(n) { cout << "Calling the constructor of Box(" << n << ")" << endl; } int main() { cout << "Entering int main();" << endl; Box b(3); cout << "Finished declaring 'Box b(3);'" << endl; Box *ptr; cout << "Finished declaring 'Box *ptr;'" << endl; ptr = new Box(5); cout << "Finished dynamic memory allocation 'new Box(5)'" << endl; delete ptr; return 0; }
The output is as follows:
Entering int main(); Calling the constructor of Box(3) Finished declaring 'Box b(3);' Finished declaring 'Box *ptr;' Calling the constructor of Box(5) Finished dynamic memory allocation 'new Box(5)'
Because ptr is simply the address of a box (a 32-/64-bit integer), there is no need to call the constructor.
The constructor in the Single_list class initializes the variables by setting the list_head and list_tail pointers to 0 and the count to 0. If this was not done, the value of these three variables would be undefined and the contents could be the previous values which were occupied by those types.
The member variables of an instance of a class must be initialized. Consider the following function which shows the different ways in which a local variable can be initialized:
#include <iostream> using namespace std; int main() { int i = 3; int j(4); cout << i << ", " << j << endl; double x = 3.141592654; cout << "Declaring 'double y(2.718281828);'" << endl; double y(2.718281828); cout << x << ", " << y << endl; return 0; }
The declaration double y(2.718281828); is similar to the previous declaration Box b(3). In the second case, the constructor for the instance b was passed the argument 3 which was duly assigned to the member variable N. In the first case, as with all built-in data types, it simply assigns the value passed to the local variable y.
For reasons which will be elaborated upon further, it is necessary to initialize all member variables before the constructor is entered. For now, simply consider this example where one member variable may be an instance of a linked list. The first instruction in the constructor may be to add something to this linked list, and consequently, the linked list must be appropriately initialized before the constructor is entered. This is done by specifying an initialization list after the constructor, as is shown in the first example above:
Box::Box( int n ):N(n) { cout << "Calling the constructor of Box(" << n << ")" << endl; }
This is paralleled by the function
int f( int n ) { int N(n); // declares N and initializes it to n return N*N; }
In the Single_list class, it is necessary to initialize all of the member variables to 0. Consequently, the constructor will look as follows:
Single_list::Single_list():list_head(0), list_tail(0), count(0) { // constructor instructions... }
Consider the following example:
#include <iostream> using namespace std; class A { private: int N; public: A( int n = 10 ):N(n) { cout << "Inside the constructor A(" << N << ")" << endl; } }; class B { private: A a; public: B() { cout << "Inside the constructor B()" << endl; } }; int main() { cout << "About to declare B b;" << endl; B b; return 0; }
When this is compiled and run, the output is:
About to declare B b; Inside the constructor A(10) Inside the constructor B()
You will note that even though A a was never explicitly initialized, the constructor for A() was called and it was called before any instructions in the constructor B() were called.
Alternatively, you could modify the class B so that it explicitly calls the constructor for A with an argument.
#include <iostream> using namespace std; class A { private: int N; public: A( int n = 10 ):N(n) { cout << "Inside the constructor A(" << N << ")" << endl; } }; class B { private: A a; public: B( int n ):a(n + 1) { cout << "Inside the constructor B(" << n << ")" << endl; } }; int main() { cout << "About to declare B b(5);" << endl; B b(5); return 0; }
When this is compiled and run, the output is:
About to declare B b(5); Inside the constructor A(6) Inside the constructor B(5)
Here, the constructor was called with an argument 6.
Many programmers new to C++ are unfamiliar with the declaration shown here
Single_list::Single_list():list_head(0), list_tail(0), count(0) { // constructor instructions... }
Many programmers new to C++ are unfamiliar with the declaration shown here
Single_list::Single_list():list_head(0), list_tail(0), count(0) { // constructor instructions... }
and constantly go out of their way to avoid it. Instead, they will use code which looks like
Single_list::Single_list() { list_head = 0; list_tail = 0; count = 0; // other constructor instructions... }
This is acceptable for member variables which are built-in data types, but this is not always possible when member variables are themselves other classes—especially when those class do not have default constructors.
In this example, the class A does not have a default constructor which takes no arguments: there is one constructor and it must take one argument. Now, when class B is given a member variable, the compiler is unaware of what it should do:
#includeusing namespace std; class A { private: int N; public: A( int n ):N(n) { cout << "Inside the constructor A()" << endl; } }; class B { private: A a; public: B() { cout << "Inside the constructor B()" << endl; } }; int main() { cout << "About to declare B b;" << endl; B b; return 0; }
When this is compiled, the compiler will note that it cannot find a zero-argument constructor for A: it only finds the constructor with one argument and the copy constructor:
{ecelinux:5} g++ example.cpp example.cpp: In constructor B::B() example.cpp:17: error: no matching function for call to A::A() example.cpp:8: note: candidates are: A::A(int) example.cpp:4: note: A::A(const A&)
One important thing to note is the order of assignment: member variables are assigned by the constructor list in the order in which they are declared and not in the order in which the constructors are called in the constructor list. An example where this becomes critical is the following:
#include <iostream> class MyArray { private: int array_size; int *array; public: MyArray( int n = 10 ):array_size( std::max( 1, n ) ), array( new int[array_size] ) { std::cout << array_size << std::endl; // empty constructor } ~MyArray() { delete [] array; } }; int main() { MyArray a1; MyArray a2( 4); MyArray a3(-5); return 0; }