Constructors and Destructors in C++ – Part 1

We’re going to think a little bit about constructing and destroying objects in C++. Let’s say we have three very simple classes, Base, Derived and DerivedAgain.

class Base
{
public:
	Base()
	{
		std::cout << "Base Constructor" << std::endl;
	}

	~Base()
	{
		std::cout << "Base Destructor" << std::endl;
	}
};

class Derived : public Base
{
public:
	Derived()
	{
		std::cout << "Derived Constructor" << std::endl;
	}

	~Derived()
	{
		std::cout << "Derived Destructor" << std::endl;
	}
};

class DerivedAgain : public Derived
{
public:
	DerivedAgain()
	{
		std::cout << "DerivedAgain Constructor" << std::endl;
	}

	~DerivedAgain()
	{
		std::cout << "DerivedAgain Destructor" << std::endl;
	}
};

Now suppose we run the following code,

int main()
{
	DerivedAgain derivedAgainObject;
	return 0;
}

The output will be:

Base Constructor
Derived Constructor 
DerivedAgain Constructor
DerivedAgain Destructor
Derived Destructor
Base Destructor

Why is this? Well, to construct the DerivedAgain object, first we must construct aDerived object and to construct a Derived object, first we must construct a Base object. Similarly, when the object goes out of scope, to destroy it, first the DerivedAgain destructor is called, then the Derived destructor and then the Base Destructor. So, when creating an object we step down through the inheritance hierarchy, and when destroying it we step back up through the hierarchy.

We can call these constructors explicitly. Indeed the above code is exactly quivalent to:

class Base
{
public:
	Base()
	{
		std::cout << "Base Constructor" << std::endl;
	}

	~Base()
	{
		std::cout << "Base Destructor" << std::endl;
	}
};

class Derived : public Base
{
public:
	Derived() : Base()
	{
		std::cout << "Derived Constructor" << std::endl;
	}

	~Derived()
	{
		std::cout << "Derived Destructor" << std::endl;
	}
};

class DerivedAgain : public Derived
{
public:
	DerivedAgain() : Derived()
	{
		std::cout << "DerivedAgain Constructor" << std::endl;
	}

	~DerivedAgain()
	{
		std::cout << "DerivedAgain Destructor" << std::endl;
	}
};

Let’s change things up a little bit, suppose we have the following situation.

class Derived : public Base
{
public:
	Derived()
	{
		std::cout << "Derived Constructor" << std::endl;
	}

	Derived(int i)
	{
		std::cout << "Derived Constructor With Parameter" << std::endl;
	}

	~Derived()
	{
		std::cout << "Derived Destructor" << std::endl;
	}
};

class DerivedAgain : public Derived
{
public:
	DerivedAgain() : Derived(5)
	{
		std::cout << "DerivedAgain Constructor" << std::endl;
	}

	~DerivedAgain()
	{
		std::cout << "DerivedAgain Destructor" << std::endl;
	}
};

Now we are explicitly calling a non-default constructor from the Derived class. All the other implicit calls to default constructors remain the same. When we run the main function as before, we will see:

Base Constructor
Derived Constructor with parameter Constructor
DerivedAgain Constructor
DerivedAgain Destructor
Derived Destructor
Base Destructor

You cannot however do something like this:

class DerivedAgain : public Derived
{
public:
	DerivedAgain() : Base()
	{
		std::cout << "DerivedAgain Constructor" << std::endl;
	}

	~DerivedAgain()
	{
		std::cout << "DerivedAgain Destructor" << std::endl;
	}
};

Here, we are trying to call the Base constructor from the DerivedAgain class. This will not compile, we can only call constructors from the classes that we directly inherit from.

So far we have only looked at constructors and destructors for local objects, that is, without using the new keyword. In part 2, we will look at what happens when we construct and destroy objects dynamically.

RAII in C++

When we are programming, we often have to deal with resources. A resource is some system functionality of limited availability, that we have to manage explicitly. This means that before we use it we have to acquire it and when we are finished we have to release it.

Examples of resources are:

  • Memory on the heap, we acquire memory on the heap with the new keyword and we must release it with the delete keyword.
  • Files, we acquire a file handle by asking the operating system to open the file, and we release it by asking for the file to be closed.
  • Mutexes, we acquire a mutex by locking it and we release a mutex by unlocking it.
  • Network or Database connections, these will have their own specific acquisition and release logic, and will usually require both.

So, let’s suppose we are using a file, and our code looks a little something like this:

public void DoSomeStuffWithAFile()
{
    ofstream file("output-file.bin");
    // do some stuff with this file
    // do some other stuff
    file.close();
}

Here, the file is our resource, we acquire it with the file constructor and we release it with the close method. However, suppose, somehow control returned from this method before we reached the final line: file.close();. Then we will not have released this resource. This can cause pretty serious problems, as resources are by definition, limited in their availability. If we use up all the file handles like this, eventually we won’t be able to open any more files!

There are two ways control could return without us using the close method. The first is that an exception is thrown somewhere between the creation of the file and the file.close() call. When this happens, control will leave the DoSomeStuffWithAFile and we will never close the file. If this exception is caught at a higher level, the file resource will be left open for the remaining duration of the program. It doesn’t really matter if an exception is thrown and not caught, because then the program will terminate and the operating system will release all the resources we held anyway.

The second is if we simply have a return statement somewhere in this method, something like:

public void DoSomeStuffWithAFile()
{
    ofstream file("output-file.bin");
    // do some stuff with this file
    if(/* some condition */)
    {
        return;
    }
    // do some other stuff
    file.close();
}

We should have a file.close() call before this return statement, but we could forget it.

How can we avoid this two potential sources of resource leakage? Well there is a pattern that handles both! This pattern is called Resource Acquisition is Initialisation. When an object is created on the stack, it is guaranteed to be torn down when control leaves the current block, as the stack is unwound. This happens whether control leaves by normal control flow or because of a thrown exception. So, we encapsulate resource acquisition and release in a class. In our example we would do something like:

class FileHolder
{
public:
    ofstream file;
    FileHolder(string fileName) : file(filename)
    {
    }

    ~FileHolder()
    {
        file.close();
    }
}

Then we use this FileHolder class like so:

public void DoSomeStuffWithAFile()
{
    FileHolder fileHolder("output-file.bin");
    // do some stuff with this file
    if(/* some condition */)
    {
        return;
    }
    // do some other stuff
}

Now, whenever control leaves the DoSomeStuffWithAFile function, the destructor will be called on the fileHolder object as the stack is unwound and the close method on our file will be called.

We can use the same pattern with any resource, we create a holder class that acquires the resource in the constructor and releases it in the destructor. That way, we are using the unwinding of the stack to control the release of our resources, which I think, is pretty cool.

In this example we have left our file object public. It would be better practice to make the internal file object private, and expose the methods we wish to use via methods on the FileHolder class. That way we control exactly how the file is used, in particular, we won’t be able to do this:

public void DoSomeStuffWithAFile()
{
    FileHolder fileHolder("output-file.bin");
    // do some stuff with this file
    fileHolder.file.close();
    // do some other stuff
}

The above code may cause our file to be closed twice, once explicitly and once as the stack is unwound. This isn’t necessarily a problem for files, but we should avoid it. For other resources, releasing twice might be a serious problem.

This pattern has other advantages. Firstly the code for acquiring and releasing it are kept together logically. Which makes the resource management logic clearer and easier to maintain. Secondly it reduces code duplication as we do not need to explicitly acquire and release a resource every time we use it.

For some resources, RAII is implemented in C++ for us already. The two most common examples are lock_guard which managers a mutex and smart pointers which manage memory.