Introduction
In this blog, I explain 3 practical purposes of virtual
keyword in C++. They are:
- to create virtual table and identify the functions to be called regardless of the type of the pointer that calls it.
- to prevent memory leak in case of missing child destructor call.
- to allow down casting
Virtual concepts
- Virtual function cannot be static.
- Virtual functions are accessed by an object pointer of base class and points to the derived
class. (This is why the running of virtual function is run-time: the CPU decides with functions
of the classes are to be called at run-time me) - The prototype of virtual functions should be the same in the base as well as the derived class.
- They are always defined in the base class and overridden in a derived class.
- It is not mandatory for the derived class to override (or re-define the virtual function), in that case, the base class version of the function is used.
- A class may have a virtual destructor but it cannot have a virtual constructor.
- Virtual keyword allows polymorphism.
Identifying the function to be called
We could have a pointer of Base class, pointing to a Derived object.
BaseClass * base_ptr = new DerivedClass();
Without using virtual, when a function, which is defined in Base class and overridden in Derived class, is called, the Base class version of the is always called. The following shows that base dummy()
is always called.
#include <iostream>
using namespace std;
class Base {
public:
Base(){ }
virtual void dummy(){ cout<<"base dummy"<<endl; }
virtual ~ Base() { }
};
class Derived1 : public Base {
public:
Derived1(){ }
virtual void dummy() { cout<<"Derived1 dummy"<<endl; }
virtual ~ Derived1() { }
};
int main() {
Base* base_ptr = new Derived1();
base_ptr->dummy(); // output: base dummy
delete base_ptr;
return 0;
}
Preventing memory leak
- We could have a Base class pointer that points at the Derived object. When the pointer is created, the constructor of both Base class and Derived class are called.
- In addition, a memory section equals to both Base class and Derived class is allocated. Virtual keyword cannot be used with constructor.
- However, when the pointer is deleted, without virtual keyword on both Base class and Derived class destructor, the Derived class destructor is not called, meaning the memory allocated for the Derived class is not free up.
The following program doesn't have virtual
keyword on the Derived class's destructor. The log output doesn't print the Derived destructor.
#include <iostream>
using namespace std;
class Base {
public:
Base(){ cout<<" base constructor "<<endl; }
~ Base() { cout<<" base destructor "<<endl; }
};
class Derived1 : public Base {
public:
Derived1(){ cout<<" Derived1 constructor "<<endl; }
virtual ~ Derived1() { cout<<" Derived1 destructor "<<endl; }
};
int main() {
Base* base_ptr = new Derived1();
delete base_ptr;
return 0;
}
// Output:
/*
base constructor
Derived1 constructor
base destructor
*/
If virtual
keyword is used on both destructors, when the pointer is deleted, the Derived's destructor is called first, and then the Base's destructor is called second.
Allowing down casting
Down casting concept
Why do we need down casting?
When we create a Base pointer and point it at a Derived class, the base pointer still cannot invoke public Derived class function.
#include <iostream>
using namespace std;
class Base {
public:
Base(){ }
virtual void dummy(){ cout<<"base dummy"<<endl; }
virtual ~ Base() { }
};
class Derived : public Base {
public:
Derived(){ }
virtual void dummy() { cout<<"Derived1 dummy"<<endl; }
virtual void derived1Func () { cout<<"derived1Func"<<endl; }
virtual ~ Derived() { }
};
int main() {
Base* base_ptr = new Derived();
base_ptr -> derived1Func();
// delete derived_ptr;
return 0;
}
If the down casting is performed correctly and successfully, the resulting object type is Derived class and can invoke Derived class function.
#include <iostream>
using namespace std;
class Base {
public:
Base(){ }
virtual void dummy(){ cout<<"base dummy"<<endl; }
virtual ~ Base() { }
};
class Derived : public Base {
public:
Derived(){ }
virtual void dummy() { cout<<"Derived1 dummy"<<endl; }
virtual void derived1Func () { cout<<"derived1Func"<<endl; }
virtual ~ Derived() { }
};
int main() {
Base* base_ptr = new Derived();
// Using dynamic_cast to safely downcast the pointer
Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr); // cannot dynamic_cast unless virtual is defined on base destructor
if(derived_ptr){
std::cout << "Downcast to Derived successful\n";
}
else {
std::cout << "Downcast to Derived failed\n";
}
derived_ptr->dummy();
derived_ptr->derived1Func();
delete derived_ptr;
return 0;
}
// Output:
/*
Downcast to Derived successful
Derived1 dummy
derived1Func
*/
Incorrect down casting
Cannot down casting Base pointer pointing at base object
We cannot have a Derived class pointer pointing at a Base class object. When we initialize the pointer in such way, implicit conversion fails. The following returns compile error: invalid conversion from ‘Base*’ to ‘Derived1*’ [-fpermissive]
Derived1 *derived2_ptr = new Base; // fail at compile time
Likewise, we could create a Base class pointer, pointing at a Base object. However, later on, when performing down casting the Base object, the operation fails at run time.
In the following program, a Base class pointer and Derived class pointer are created.
- Base class pointer points at the Base object
- Derived class pointer is created, but not yet allocated.
- Base class pointer is down casted, then Derived class pointer is assigned to the supposedly down casted pointer.
- However, Base class pointer down casting operation already fails.
#include <iostream>
using namespace std;
class Base {
public:
Base(){ }
virtual void dummy(){ cout<<"base dummy"<<endl; }
virtual ~ Base() { }
};
class Derived : public Base {
public:
Derived(){ }
virtual void dummy() { cout<<"Derived1 dummy"<<endl; }
virtual void derived1Func () { cout<<"derived1Func"<<endl; }
virtual ~ Derived() { }
};
int main() {
Base* base_ptr = new Base(); // declare base_ptr pointing at Base obj
// Derived1 *derived2_ptr = new Base; // cannot point child to parent object, implicit conversion is done at compile time
Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr); // compile successfully, but fail conversion at run-time
if (derived_ptr) { std::cout << "Downcast to Derived2 successful\n";
}
else {
std::cout << "Downcast to Derived failed\n";
}
return 0;
}
// Output:
// Downcast to Derived failed
Cannot down casting if Base class is not polymorphic
In addition we need virtual
keyword on the destructor to ensure successful down casting.
The following program has virtual keyword used on every function in Derived class, including the destructor.
However, the Base class doesn't have virtual keyword, thus Base class is not polymorphic.
Compiling the following program gives error: cannot ‘dynamic_cast’ ‘base_ptr’ (of type ‘class Base’) to type ‘class Derived’ (source type is not polymorphic)
#include <iostream>
using namespace std;
class Base {
public:
Base(){ }
void dummy(){}
~ Base() { }
};
class Derived : public Base {
public:
Derived(){ }
virtual void dummy() { }
virtual void derived1Func () { }
virtual ~ Derived() { }
};
int main() {
Base* base_ptr = new Derived();
// Using dynamic_cast to safely downcast the pointer
Derived* derived_ptr = dynamic_cast<Derived*>(base_ptr); // cannot dynamic_cast unless virtual is defined on base destructor
if(derived_ptr){
std::cout << "Downcast to Derived successful\n";
}
else {
std::cout << "Downcast to Derived failed\n";
}
delete derived_ptr;
return 0;
}