When we work with inheritance, the order of calling constructors and destructors can be summarized as the following:
- Construction: Base class c’tor then Derived Class c’tor
- Destruction: Derived class d’tor then Base class d’tor
Additionally, it’s a convenient feature to be able to use base class pointers to point to derived class objects, but we need to take some precautions. Here are some examples I experimented with regarding virtual functions and inheritance which will validate the idea why it’s always a good idea to have virtual destructors declared in the Base class.
Scenario 1: Derived class pointer -> Derived object
#include <iostream>
class Base{
public:
Base() {
std::cout << "Base C'tor" << std::endl;
}
~Base() {
std::cout << "Base D'tor" << std::endl;
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived C'tor" << std::endl;
}
~Derived() {
std::cout << "Derived D'tor" << std::endl;
}
};
int main()
{
Derived* dp = new Derived();
delete dp;
return 0;
}
The output of this code is what we desire:
Base C'tor
Derived C'tor
Derived D'tor
Base D'tor
Scenario 2: Base class pointer -> Derived object
#include <iostream>
class Base{
public:
Base() {
std::cout << "Base C'tor" << std::endl;
}
~Base() {
std::cout << "Base D'tor" << std::endl;
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived C'tor" << std::endl;
}
~Derived() {
std::cout << "Derived D'tor" << std::endl;
}
};
int main()
{
Base* bp = new Derived();
delete bp;
return 0;
}
The result of executing this code is:
Base C'tor
Derived C'tor
Base D'tor
Notice how the Derived class destructor isn’t called, even though if was defined. This could lead to memory leaks because the Derived class is not properly cleaned up.
When the delete
keyword is employed to remove a pointer instance, the pointer's type is considered rather than its context. Given that the pointer's type is of the base class, only the base class's destructor will be triggered.
Scenario 3: Making Destructor Virtual in Base Class
We can fix the issue in scenario 2 by making the base class destructor virtual. That way during runtime, the derived class will guarantee to be invoked.
#include <iostream>
class Base{
public:
Base() {
std::cout << "Base C'tor" << std::endl;
}
virtual ~Base() {
std::cout << "Base D'tor" << std::endl;
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived C'tor" << std::endl;
}
~Derived() {
std::cout << "Derived D'tor" << std::endl;
}
};
int main()
{
Base* bp = new Derived();
delete bp;
return 0;
}
Now we have our desired result:
Base C'tor
Derived C'tor
Derived D'tor
Base D'tor
Extra: What if I privately inherit Base class instead?
#include <iostream>
class Base{
public:
Base() {
std::cout << "Base C'tor" << std::endl;
}
virtual ~Base() {
std::cout << "Base D'tor" << std::endl;
}
};
class Derived : Base {
public:
Derived() {
std::cout << "Derived C'tor" << std::endl;
}
~Derived() {
std::cout << "Derived D'tor" << std::endl;
}
};
int main()
{
Base* bp = new Derived();
delete bp;
return 0;
}
This will result in an error, because outside of Derived class, no one else knows that Derived inherited Base.
main.cpp: In function ‘int main()’:
main.cpp:35:28: error: ‘Base’ is an inaccessible base of ‘Derived’
35 | Base* bp = new Derived();
| ^
However, this would work fine if we use a Derived class pointer to point to Derived object.
#include <iostream>
class Base{
public:
Base() {
std::cout << "Base C'tor" << std::endl;
}
virtual ~Base() {
std::cout << "Base D'tor" << std::endl;
}
};
class Derived : Base {
public:
Derived() {
std::cout << "Derived C'tor" << std::endl;
}
~Derived() {
std::cout << "Derived D'tor" << std::endl;
}
};
int main()
{
Derived* dp = new Derived();
delete dp;
return 0;
}
This will yield the expected behavior:
Base C'tor
Derived C'tor
Derived D'tor
Base D'tor
In conclusion, when we intend to use base class pointers with derived class we want to make sure that:
- Virtual destructor is declared in Base class
- Derived class is publicly inherited