Tuesday, January 18, 2011

Virtual destructors, virtual assignment, and overriding virtualization

Virtual destructors
Although C++ provides a default destructor for your classes if you do not provide one yourself, it is sometimes the case that you will want to provide your own destructor (particularly if the class needs to deallocate memory). You should always make your destructors virtual if you’re dealing with inheritance. Consider the following example:
01class Base
02{
03public:
04    ~Base()
05    {
06        cout << "Calling ~Base()" << endl;
07    }
08};
09 
10class Derived: public Base
11{
12private:
13    int* m_pnArray;
14 
15public:
16    Derived(int nLength)
17    {
18        m_pnArray = new int[nLength];
19    }
20 
21    ~Derived() // note: not virtual
22    {
23        cout << "Calling ~Derived()" << endl;
24        delete[] m_pnArray;
25    }
26};
27 
28int main()
29{
30    Derived *pDerived = new Derived(5);
31    Base *pBase = pDerived;
32    delete pBase;
33 
34    return 0;
35}
Because pBase is a Base pointer, when pBase is deleted, the program looks to see if the Base destructor is virtual. It’s not, so it assumes it only needs to call the Base destructor. We can see this in the fact that the above example prints:
Calling ~Base()
However, we really want the delete function to call Derived’s destructor (which will call Base’s destructor in turn). We do this by making Base’s destructor virtual:
01class Base
02{
03public:
04    virtual ~Base()
05    {
06        cout << "Calling ~Base()" << endl;
07    }
08};
09 
10class Derived: public Base
11{
12private:
13    int* m_pnArray;
14 
15public:
16    Derived(int nLength)
17    {
18        m_pnArray = new int[nLength];
19    }
20 
21    virtual ~Derived()
22    {
23        cout << "Calling ~Derived()" << endl;
24        delete[] m_pnArray;
25    }
26};
27 
28int main()
29{
30    Derived *pDerived = new Derived(5);
31    Base *pBase = pDerived;
32    delete pBase;
33 
34    return 0;
35}
Now this program produces the following result:
Calling ~Derived()
Calling ~Base()
Again, whenever you are dealing with inheritance, you should make your destructors virtual.
Virtual assignment
It is possible to make the assignment operator virtual. However, unlike the destructor case where virtualization is always a good idea, virtualizing the assignment operator really opens up a bag full of worms and gets into some advanced topics outside of the scope of this tutorial. Consequently, we are going to recommend you leave your assignments non-virtual for now, in the interest of simplicity.
Overriding virtualization
Very rarely you may want to override the virtualization of a function. For example, consider the following code:
01class Base
02{
03public:
04    virtual const char* GetName() { return "Base"; }
05};
06 
07class Derived: public Base
08{
09public
10    virtual const char* GetName() { return "Derived"; }
11};
There may be cases where you want a Base pointer to a Derived object to call Base::GetName() instead of Derived::GetName(). To do so, simply use the scope resolution operator:
1int main()
2{
3    Derived cDerived;
4    Base &rBase = cDerived;
5    // Calls Base::GetName() instead of the virtualized Derived::GetName()
6    cout << rBase.Base::GetName() << endl;
7}
You probably won’t use this very often, but it’s good to know it’s at least possible.
The downside of virtual functions
Since most of the time you’ll want your functions to be virtual, why not just make all functions virtual? The answer is because it’s inefficient — resolving a virtual function call takes longer than a resolving a regular one. Furthermore, the compiler also has to allocate an extra pointer for each class object that has one or more virtual functions. We’ll talk about this more in the next couple of lessons.

No comments: