C++ Pure Virtual Destructor
- Call the Derived Class Function Using the Base Class Pointer
- Pure Virtual Function and Abstract Class
- Virtual and Pure Virtual Destructor
- Pure Virtual Destructor
In this tutorial, we will discuss the pure virtual destructors in C++. However, to fully grasp the concept, we must understand different related concepts.
First, we will see the method to call a derived class function using a base class pointer, and then we will discuss issues of destructor calling in inheritance. Finally, we will discuss pure virtual destructor and its implementation issues.
Call the Derived Class Function Using the Base Class Pointer
A base class pointer has the power to point to any of its derived class objects. This saves us from declaring and managing multiple derived class pointers for handling different derived class objects.
However, a base class pointer can’t call the derived class methods in general. For example, see the following code:
#include <iostream>
using namespace std;
class P {};
class C : public P {
public:
void f() { cout << "Function C\n"; }
};
int main() {
P *ptr = new C;
ptr->f();
return 0;
}
On compilation, this code shows the following error:
virtual_destructor0.cpp: In function ‘int main()’:
virtual_destructor0.cpp:12:7: error: ‘class P’ has no member named ‘f’
ptr->f();
If we declare the same function in the base class, the compiler calls the base class function instead of the derived class function. See next code, where we have added function f
in class P
:
#include <iostream>
using namespace std;
class P {
public:
void f() { cout << "Function P\n"; }
};
class C : public P {
public:
void f() { cout << "Function C\n"; }
};
int main() {
P *ptr = new C;
ptr->f();
return 0;
}
After adding function f
, see the output:
Function P
This means the base class pointer calls the base class function, not the derived class function. We deduce the rule that a class pointer can call a function from its class, not from the containing object’s class.
However, there is another crucial concept called Virtual Function. The virtual function allows derived classes to override the base class function, which the base class pointer can call.
Function overriding means the derived class provides its own functionality for the corresponding base class function. The base class writes the keyword virtual
with its functions to allow overriding in the derived class.
Again, see the code:
#include <iostream>
using namespace std;
class P {
public:
virtual void f() { cout << "Function P\n"; }
};
class C : public P {
public:
void f() { cout << "Function C\n"; }
};
int main() {
P *ptr = new C;
ptr->f();
return 0;
}
Note the addition of the virtual
keyword before the base class function f()
. After the new addition, see the output:
Function C
This means that the base class pointer is now calling a function according to the type of object referred to by this base pointer (i.e., the member of the derived class will execute). Remember, earlier behavior was to call members of pointer type (i.e., base class).
However, it is the derived class implementer’s choice to give the virtual class functionality, which means the derived class may or may not override the virtual base function.
Sometimes, the architect (designer) of classes enforces a derived class to implement base class functions, which involves more concepts like abstract base class (also called ABC) and pure virtual function.
We will quickly discuss them. You may visit the referred websites if required.
Pure Virtual Function and Abstract Class
The base classes can declare a pure virtual function to enforce the implementation of virtual base methods in derived classes. A pure virtual function has no implementation.
Moreover, a class having one or more pure virtual functions becomes an abstract class. Abstract classes are not instantiable (however, we can declare their pointers, pointing to derived class objects).
The syntax of a pure virtual function is slightly different:
virtual void f() = 0;
The pure virtual function has no definition or body; therefore, it is not callable. However, it enforces derived classes to implement pure virtual functions.
Otherwise, the derived class becomes an abstract (i.e., non-instantiable).
Conversely, to make a class abstract, we only need to make one abstract function. We will refer to this sentence later in the article.
Virtual and Pure Virtual Destructor
First of all, please note that the destructor can’t be overridden. However, to call both base and child class destructors, the destructor in the pointer’s (base) class should be virtual.
Otherwise, only the child class destructor will be called. For example, let’s look at the following code:
#include <iostream>
using namespace std;
class P {
public:
~P() { cout << "P Class Destructor\n"; }
};
class C : public P {
public:
~C() { cout << "C Class Destructor\n"; }
};
int main() {
P *ptr = new C;
delete ptr;
return 0;
}
Output:
P Class Destructor
Here, you can see that pointer is of class P
, and the object is of class C
. The delete
operation calls only the base class destructor, which means if we have to delete resources of the derived class, we cannot do that.
For this purpose, we must declare a virtual destructor in the base class. As you already know, for this, we have to add a virtual
keyword with the base class destructor.
Here is the sample code:
#include <iostream>
using namespace std;
class P {
public:
virtual ~P() { cout << "P Class Destructor\n"; }
};
class C : public P {
public:
~C() { cout << "C Class Destructor\n"; }
};
int main() {
P *ptr = new C;
delete ptr;
return 0;
}
Output:
C Class Destructor
P Class Destructor
Pure Virtual Destructor
Finally, coming to the main topic, let’s solve the issue of calling destructors of both parent and derived classes through a pure virtual destructor.
As mentioned earlier, a destructor differs from other functions, and there is no concept of overriding the destructor. Therefore, a pure virtual destructor can’t force a derived class to implement a destructor.
Now, let’s view things from another angle. To make a class abstract, we must declare at least one pure virtual function.
In such a case, the derived classes will be bound to implement the pure virtual function to become a concrete class. So, we make a class abstract by declaring a pure virtual destructor in this class, which will not force the derived class to implement anything.
Odds of a Pure Virtual Destructor
Always remember the No Free Lunch
theorem; declaring a pure virtual destructor has some complications.
Let’s see these through the following example:
class P {
public:
virtual ~P() = 0;
};
If we compile this code, the following error occurs:
/tmp/ccZfsvAh.o: In function `C::~C()':
virtual_destructor4.cpp:(.text._ZN1CD2Ev[_ZN1CD5Ev]+0x22): undefined reference to `P::~P()'
collect2: error: ld returned 1 exit status
Luckily, most of the complex problems in the world have simple solutions. This error occurs because the compiler is looking for the definition of the destructor, which is unavailable.
Therefore, the solution is to define the pure virtual destructor. Seems a contradiction; compiler demanding a definition of a pure virtual function?
Yes, this is among some of the other odds of C++. We can’t implement pure virtual destructor inside the class; however, we can implement it outside the class.
Look at the following code:
class P {
public:
virtual ~P() = 0;
};
P::~P() { cout << "P Class Destructor\n"; }
In the last line, we have defined a pure virtual destructor outside the class, and there will be no compiler error. Also, we will be able to call both base class and derived class destructor:
C Class Destructor
P Class Destructor
To conclude, to make an abstract base class, we can declare a pure virtual destructor inside it; however, we must define this pure virtual destructor outside the class (where we can clean any resource/ memory). This way, we can call both base class and derived class destructor.