Cpp ~ Extra Things - acquaman/acquaman GitHub Wiki

This page defines a few known 'gotchas' that can be encountered when programming C++, as well as a few tips that can help avoid them. In general the format of this page will start each section with a 'short version' and then an explanation as to why this particular piece of advice stands.

This page currently applies to code written in the C++98/03 standard (as Acquaman is)

Virtual Destructors

Short Version

Public destructors for any class that is derived from should be marked as virtual

Why?

In fact, the short version of this could be a little longer:

A base class destructor should be either public and virtual, or protected and nonvirtual. - Herb Sutter

...and a more in depth analysis of virtual classes than I could ever provide can be found here

For Acquaman, however, it is sufficient to say that for all classes that are intended to be polymorphically deleted, it is necessary to mark the base class destructor as virtual. Failing to do so will invoke undefined behaviour when deleting a derived class stored in a base class pointer. eg.

class NonVirtualBase {
    // Constrcutor
    NonVirtualBase();
    // Copy Constructor
    NonVirtualBase(const NonVirtualBase& other);
    // NON VIRTUAL desctructor
    ~NonVirtualBase();
};

class Derived : public NonVirtualBase {
    // Constructor
    Derived();
    // Copy Constructor
    Derived(const Derived& other);
    // ~Derived();
};

In this case initializing a Derived* and storing it in a NonVirtualBase*, then deleting it from NonVirtualBase* will cause undefined behaviour.

    NonVirtualBase* b;
    b = new Derived();
    delete b;    // Undefined Behaviour!!

In order to fix this issue, the destructor of the base class needs to be marked virtual:

class VirtualBase {
    // Constrcutor
    VirtualBase();
    // Copy Constructor
    VirtualBase(const VirtualBase& other);
    // VIRTUAL desctructor
    virtual ~VirtualBase();
};

class Derived : public VirtualBase {
    // Constructor
    Derived();
    // Copy Constructor
    Derived(const Derived& other);
    // Destructor
    ~Derived();
};

In our updated example of VirtualBase above, the destructor of VirtualBase is marked as virtual, and thus calling the following code is valid:

    VirtualBase* b;
    b = new Derived();
    delete b;    // Safe!!

Explicit Constructors

Short Version

Always mark constructors that take a single argument (but are not copy constructors) with the explicit keyword, especially if the argument is of a primitive type.

Why?

Often, both within Qt and Acquaman, constructors will be marked as explicit. This is a keyword used to prevent bugs which can be very hard to track down. In essence the explicit keyword stops the implicit conversion from the argument type, to the constructor's class type.

Okay, so take the example where we have a class, with a constructor that takes a primitive type as an argument, and is not marked as explicit:

class SomeClass {
public:
    SomeClass(int i); /// Not marked as explicit!
};

then imagine we have two other headers, somewhere else in system, each of which defines a function "doSomething", one taking an integer argument, and the other a SomeClass argument:

Header1.h

void doSomething(const SomeClass& someClass) { [...] }

Header2.h

void doSomething(int i) { [...] }

Then, in a final section of code somewhere else, a call is made to doSomething with an integer argument:

doSomething(10);

Imagine then that I want to remove the doSomething(int) function (possibly without ever realizing that a SomeClass version exists), I expect that if I comment out the int version, then compile, I should get compile time errors indicating all the places where doSomething(int) is called. Unfortunately the implicit conversion constructor of SomeClass prevents this happening. The compiler, no longer finding a straight version of doSomething which takes an int argument, will try its best to find a match. In our case it will find the other version of our doSomething function and see that it can do an implicit conversion from int to someClass, and thus meet the requirements of the other version. It will implicitly call the constructor of SomeClass which takes the integer argument, and then pass the result to doSomething(const SomeClass& someClass). Not what we intended at all, and very easy to miss. In this case, the problem can be solved simply by marking any constructor that takes a single argument (or a single non default argument) with the explicit keyword, especially if that argument type is primitive (int, double, etc.). This does not, however, apply to copy constructors (doing so will break a lot of the implicit places in which copy constructors are called).