Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding virtual destructors

Tags:

c++

gcc

I was trying to familiarize myself with the OOP concepts but could not quite understand the concept of virtual.

  1. One can create a virtual destructor but not a virtual constructor. Why?
  2. How are virtual destructors handled internally? I mean the link Virtual Destructors illustrates the concept but my question is how the vptr of both the vtables (Derived and Base) are called? (In case of virtual member functions when such a scenario occurs generally the function that vptr of Derived class points to is only called)
  3. Are there any other scenarios where one may need to use a virtual destructor?

Can anyone please help me understand the above concepts with links/examples?

like image 877
SohamC Avatar asked Sep 11 '14 06:09

SohamC


1 Answers

First, a little about the difference between virtual functions and non-virtual functions:

Every non-virtual function-call that you have in your code can be resolved during compilation or linkage.

By resolved, we mean that the address of the function can be computed by the compiler or the linker.

So in the object code created, the function-call can be replaced with an op-code for jumping to the address of that function in memory.

With virtual functions, you have the ability to invoke functions which can be resolved only during runtime.

Instead of explaining it, let's run through a simple scenario:

class Animal
{
    virtual void Eat(int amount) = 0;
};

class Lion : public Animal
{
    virtual void Eat(int amount) { ... }
};

class Tiger : public Animal
{
    virtual void Eat(int amount) { ... }
};

class Tigon : public Animal
{
    virtual void Eat(int amount) { ... }
};

class Liger : public Animal
{
    virtual void Eat(int amount) { ... }
};

void Safari(Animal* animals[], int numOfAnimals, int amount)
{
    for (int i=0; i<numOfAnimals; i++)
        animals[i]->Eat(amount);
    // A different function may execute at each iteration
}

As you can probably understand, the Safari function allows you to be flexible and feed different animals.

But since the exact type of each animal is not known until runtime, so is the exact Eat function to be called.


The constructor of a class cannot be virtual because:

Calling a virtual function of an object is performed through the V-Table of the object's class.

Every object holds a pointer to the V-Table of its class, but this pointer is initialized only at runtime, when the object is created.

In other words, this pointer is initialized only when the constructor is called, and therefore the constructor itself cannot be virtual.

Besides that, there is no sense for the constructor to be virtual in the first place.

The idea behind virtual functions is that you can call them without knowing the exact type of the object with which they are called.

When you create an object (i.e., when you implicitly call a constructor), you know exactly what type of object you are creating, so you have no need for this mechanism.


The destructor of a base-class has to be virtual because:

When you statically allocate an object whose class inherits from the base-class, then at the end of the function (if the object is local) or the program (if the object is global), the destructor of the class is automatically invoked, and in turn, invokes the destructor of the base-class.

In this case, there is no meaning to the fact that the destructor is virtual.

On the other hand, when you dynamically allocate (new) an object whose class inherits from the base-class, then you need to dynamically deallocate (delete) it at some later point in the execution of the program.

The delete operator takes a pointer to the object, where the pointer's type may be the base-class itself.

In such case, if the destructor is virtual, then the delete operator invokes the destructor of the class, which in turn invokes the destructor of the base-class.

But if the destructor is not virtual, then the delete operator invokes the destructor of the base-class, and the destructor of the actual class is never invoked.

Consider the following example:

class A
{
    A() {...}
    ~A() {...}
};

class B: public A
{
    B() {...}
    ~B() {...}
};

void func()
{
    A* b = new B(); // must invoke the destructor of class 'B' at some later point
    ...
    delete b; // the destructor of class 'B' is never invoked
}
like image 61
barak manos Avatar answered Sep 30 '22 20:09

barak manos