c++optimizationdestructor

Is the C++ compiler optimizer allowed to break my destructor ability to be called multiple times?


We once had an interview with a very experienced C++ developer who couldn't answer the following question: is it necessary to call the base class destructor from the derived class destructor in C++?

Obviously the answer is no, C++ will call the base class destructor automagically anyway. But what if we attempt to do the call? As I see it the result will depend on whether the base class destructor can be called twice without invoking erroneous behavior.

For example in this case:

class BaseSafe {
public:
    ~BaseSafe()
    {
    }
private:
    int data;
};

class DerivedSafe {
public:
    ~DerivedSafe()
    {
        BaseSafe::~BaseSafe();
    }
};

everything will be fine - the BaseSafe destructor can be called twice safely and the program will run allright.

But in this case:

class BaseUnsafe {
public:
    BaseUnsafe()
    {
       buffer = new char[100];
    }
    ~BaseUnsafe ()
    {
        delete[] buffer;
    }
private:
    char* buffer;
};

class DerivedUnsafe {
public:
    ~DerivedUnsafe ()
    {
        BaseUnsafe::~BaseUnsafe();
    }
};

the explicic call will run fine, but then the implicit (automagic) call to the destructor will trigger double-delete and undefined behavior.

Looks like it is easy to avoid the UB in the second case. Just set buffer to null pointer after delete[].

But will this help? I mean the destructor is expected to only be run once on a fully constructed object, so the optimizer could decide that setting buffer to null pointer makes no sense and eliminate that code exposing the program to double-delete.

Is the compiler allowed to do that?


Solution

  • Standard 12.4/14

    Once a destructor is invoked for an object, the object no longer exists; the behavior is undefined if the destructor is invoked for an object whose lifetime has ended (3.8).

    So I guess the compiler should be free to optimize away the setting of buffer to null since the object no longer exists after calling the destructor.

    But even if the setting of the buffer to null wasn't removed by the compiler, it seems like calling the destructor twice would result in UB.