Virtual keyword in C++

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;
}

Leave a Reply

Your email address will not be published. Required fields are marked *