Declare destructors virtual in a polymorphic base classes

let’s start with a simple example from Effective C++ Third Edition.

TimeKeeper Example
  1. class TimeKeeper
  2. {
  3. public:
  4.     TimeKeeper();
  5.     ~TimeKeeper();
  6. };
  7. class AtomicClock: public TimeKeeper{};
  8. class WaterClock : public TimeKeeper{};
  9. class WritstWatch: public TimeKeeper{};

for managing the creation of objects will create a factory function that returns a dynamically allocated object of a class derived from “TimeKeeper”.

  1. TimeKeeper* getTimeKeeper();

The objects returned by this functions are on the heap memory, so to avoid leaking the memory and other resources it’s important to clean up these objects constantly after using them.

  1. TimeKeeper *ptk = getTimeKeeper();
  2. delete ptk;

Suppose that (getTimeKeeper) returns a pointer to a derived class object (ex. AtomicClock), this object is being deleted via a base class pointer and the base class (TimeKeeper) has a non-virtual destructor, this may lead to a disaster as C++ specifies that when a derived class object is deleted through a pointer to a base class with a non-virtual destructor, results are undefined.
What happens in runtime is that the derived part of the object is never destroyed, the data members of the derived class object probably not to be destroyed nor the destructor run, although the base class part will be destroyed and this will lead to a partially destroyed object, and this is a way of leaking resources.
Eliminating this problem is very simple, by giving the base class a virtual destructor  like this.

  1. class TimeKeeper
  2. {
  3. public:
  4.     TimeKeeper();
  5.     virtual ~TimeKeeper();
  6. };
  7. class AtomicClock: public TimeKeeper{};
  8. class WaterClock : public TimeKeeper{};
  9. class WritstWatch: public TimeKeeper{};
  10. TimeKeeper *ptk = getTimeKeeper();
  11. delete ptk;  // now behaves correctly

If a class does not contain virtual functions, that often indicates it won’t be used as a base class. When a class is not intended to be a polymorphic class, making a virtual destructor is usually a bad idea.

Point Class Example
  1. class Point
  2. {
  3. public:
  4.     Point(int xCoord, int yCoord);
  5.     ~Point();
  6. private:
  7.     int x, int y;
  8. };

if an int occupies 32 bits, a Point class object can fit into 64-bit register, the implementation of virtual functions requires the objects carry information that can be used at runtime to determine which virtual functions should be invoked on the object.

This information typically takes the form of a pointer called vptr  (Virtual Table Pointer) , this pointer points to an array of function pointers called vtbl (Virtual Table) each class with virtual functions has an associated vtbl, when a virtual functions is invoked on an object, the actual function called is determined by following the object’s vptr to a vtbl and then looking up the appropriate function pointer in vtbl.

Regarding the class “Point” if the it contains a virtual function, objects of type “Point” will increase in size, on 32-bit architecture, they’ll go from 64 bits (for the two ints x,y) to 96 bits (for the ints + vptr) and from 64 to 128 bits in 64-bit architecture, because pointer on these architectures are 64-bit size, by adding  vptr to “Point” the size will be increased by 50-100% ! No longer can “Point” objects fit in a 64-bit register.

We can summarize this topic in two points

Polymorphic base classes should declare virtual desturcotrs, If a class has any virtual functions, it should have a virtual destructor.

Classes not designed be used polymorphically should not declare virtual destructors.

But take care NOT all base classes are designed to be used polymorphically, for example standard string.

  1. class SpecialString : public std::string{ …. };

This is a bad idea as std::string has non-virtual destructor and not designed to be used polymorphically and if anyone tried to convert pointer to SpeicalString to pointer to std::string and then used delete this will lead to undefined behavior and SpecialString resources will be leaked as the destructor won’t be called, and you’ll find this -lacking of virtual destructor- in all STL types also (e.g vector, list, set, … etc).Unfortunately,  C++ doesn’t offers derivation-prevention mechanism in contrast to Java’s final classes or C#’s sealed classes.

So, DON’T forget Not all base classes are designed to be used polymorphically.

  • Effective C++, Third Edition —Scott Meyers.
  • Professional C++— Nicholas A. Solter Scott J. Kleper. 

Constructors & Destructors in C++

Most of classes we write contain one or more constructors and destructors, which are responsible for controlling the fundamental operations of bringing new object into existence and making sure it’s initialized and getting rid of an object and making sure it’s cleaned up. Making mistakes in these functions will lead to unremarkable serious errors throughout the implemented classes, so in this post we are going to discuss some tips and tricks about how to deal with constructors and destructors that help in writing efficient C++ code.

Silent Calling and Writing of functions

It seems that you may implement an empty class in C++ like this

class EmptyClass{…};

actually this is not an empty  class as the compiler will generate its own version of copy constructor and copy assignment operator, moreover if you didn’t declare constructor the compiler will declare a default constructor and all of these functions will be public and inline ,so the previous line of code and the following code snippet are considered to be the same.

  1. class Empty {
  2. public:
  3.     Empty() { … }    // default constructor
  4.     Empty(const Empty& rhs) { … }// copy constructor
  5.     ~Empty() { … }// destructor
  6.     // for whether it’s virtual
  7.     Empty& operator=(const Empty& rhs) { … } // copy assignment operator
  8. }

The following code will cause these functions to be generated

Empty e1; // default constructor, destructor
Empty e2(e1); // Copy constructor
e2 = e1; // Copy assignment operator

We can ask here, what should these functions do ?!
Regarding default Constructor and Destructor, they give the compiler a place to put behind the scenes code such as invocation of constructor and destructor of base classes and non-static data members, and this generated desturctor is non-virtual unless it’s for a class inheriting from base class that itself  declares virtual destructor.

And regarding copy constructor and the copy assignment operator, the compiler-generated code simply copy each non-static data member of the source object to the target object.We can summarize all of this in one sentence

Compilers may implicitly generate a class’s default constructor, copy constructor, copy assignment operator and destructor.

From this sense we can ask how to disallow the use of compiler-generated functions that we don’t want ?!

The answer is very simple, we’ll declare the corresponding member functions private and won’t give them an implementation.

Class Test Example
  1. class Test
  2. {
  3. public:
  4.   //public members
  5. private:
  6.     Test (const Test&);
  7.     Test& operator= (const Test&);
  8. };

The user won’t be able to copy “Test” object , and if there’s a trial to copy an object the linker will complain and the error will be “can’t find appropiate default constructor”, but it’s possible to move the link-time error up to compile-time where the earlier error detection is always better than later by declaring the constructor and copy assignment operator private not in “Test” class but in a base class designed specifically to prevent copying

Modified Test Example
  1. class UnCopyable
  2. {
  3. protected:
  4.     UnCopyable(){}
  5.     ~UnCopyable(){}
  6. private:
  7.     UnCopyable (const UnCopyable&);
  8.     UnCopyable& operator= (const UnCopyable&);
  9. };
  10. class A : public UnCopyable
  11. {
  12. // no longer defines ctors or copy assignment operator
  13. };

The compiler will try to generate a copy constructor or copy assignment operator if any body tries to copy a “Test” object, the compiler-generated versions of these function will try to call the base class functions, and this will be rejected as the copying operations are private.

To disallow functionality automatically provided by compilers, declare the corresponding member functions private and give no implementations.

That’s enough for today ! To Be Continued …

  • Effective C++, Third Edition —Scott Meyers.
  • Professional C++— Nicholas A. Solter Scott J. Kleper.