18 April 2009

Why always use virtaul D'tor in C++

I've been asked more than one time this question, and I faced some problems when programming in C++ that using virtual D'tor was the solution of them. So here is why...

1. In order to create virtual table.

Explanation:
Sometimes we need to try dynamic_cast<> or use the typeid() in order to know the object type, both of these operator succeed ONLY if the object has virtual table, therefore, if the class type of the object used has no virtual function in his definition, using these operators will fail (dynamic_cast<> will always return null and typeid() will always return the base class).

Solution:
add virtual D'tor to create the virtual table and then both operators will succeed (class will be polymorphic type)

Example:
//this will fail
class A {}
class B : A {}
A a;
B b;
if( typeid(b) == typeid(B) )
   //typeid(b) will return A, while typeid(B) will return B.
{ cout << "success"; }
else
{ cout << "fail"; }

//This will success
class A { virtual ~A(){} }
class B : A {}
A a;
B b;
if( typeid(b) == typeid(B) )
   // both typeid(b) and typeid(B) will return B.
{ cout << "success"; }
else
{ cout << "fail"; }

2. Call the suitable D'tor.

Explanation:
In some cases, dynamic dispatching of the D'tor is needed (call the dynamic type d'tor of the object)

Solution:
Add virtual D'tor for dynamic dispatching

Example:
//A::~A() will be printed
class A{ ~A(){ cout << "A::~A()"; }
class B{ ~B(){ cout << "B::~B()"; }
A *a = new B();
//end

//B::~B() will be printed
class A{ virtual ~A(){ cout << "A::~A()"; }
class B{ ~B(){ cout << "B::~B()"; }
A *a = new B();
//end

3. call operator delete with the suitable size.

Explanation:
For most programming tasks, the default implementation of operators new and delete is sufficient. In fact, many C++ programmers get along without even knowing that they can override these operators. So, operator delete (which differs from operator delete!) can be overridden.
Note: delete operator is a keyword in C++ (the one turn into blue color when typed), and operator delete is the operator called automatically when we use delete operator. What happens is: when delete operator is called, first the D'tor is the object is called and then operator delete is called.

This operator has two possible overrides, the first, which is the common one, is:
    static void operator delete (void *p);
and the other is:
    static void operator delete (void *p, size_t size);
The second option is needed when operators new and delete are overridden in base and derived classes, so the implementation of operator delete can know what's the object type called it (using sizeof(size) ).
The problem is: if the class D'tor is not virtual, the size passed to the operator delete is base class size and not the dynamic class size (which can be the derived one). And then, wrong flow of operator delete implementation can happen.

Solution:
Use virtual D'tor, that assures the size passed to operator delete is the size of the dynamic type of the caller.

Example:
class base{ public: void operator delete(void *, size_t); };
class derived : base{};
base *ptr = new derived();
    //This will be compiled to the following:
    // base::operator new(ptr, sizeof(derived));
    // ptr->base();
/*
do whatever you want with ptr...
...
...
*/
delete ptr;
    //This will be compiled to the following:
    // ptr->~base();
    // base::operator delete(ptr, sizeof(base));

/*
Note that the size passed to new operator is the sizeof(derived) class and the size passed to delete operator is the sizeof(base) class
*/

For these three reason, I always use Virtual D'tor although it don't solve any of the solution above. Who knows, one day you may need it.

 

No comments: