Search code examples
c++c++11interfacevirtualdestructor

Destructor from class implementing interface not called when referencing as interface


I have a basic interface (using Microsofts C++ syntax for interfaces in Visual Studio 2013) which exposes simple functions like this:

__interface IDisposable {
    void Dispose();
};
__interface IBase : public IDisposable {
    void Foo();
    void Bar();
};

There is a specific type of classes which has to inherit those methods thus these classes have this structure:

class Derived : public IBase {
public:
    Derived();
    ~Derived();
    void Dispose();
    void Foo();
    void Bar();
}

These classes do have some variables as well and I wanted to use smart pointers but those pointers did produce leaks because the destructor ~Derived() isn't called in the following scenario:

  • I am having a Stack<IBase*> which handles a bunch of those objects (e.g. Derived, Derived2, etc.).
  • Once the top-most element (pCurrent) should be removed from the stack with the corresponding data (m_Data) I am calling the method to dispose resources: ((IDisposable*)pCurrent->m_Data)->Dispose();
  • This call is followed by a delete pCurrent->m_Data which apparently tries to invoke ~IBase() which doesn't exist obviously as it is an interface. Therefore the mentioned ~Derived() won't be called as well and any smart pointers in Derived would not be deleted as well and this causes serious memory leaks.

Surprisingly a manual deletion works:

auto p = new Derived();
delete p; // ~Derived() is properly called as we are not handling a IBase* object

I was thinking about using a virtual destructor, but there is no way to define destructors in those interfaces to make ~Derived() virtual as well.

Is there a better approch to be able to call the right destructors?


Solution

  • The problem as I mentioned in the comments is that you don't have a virtual destructor in the bottom of your polymorphic hierarchy, therefore you get undefined behavior when you delete IBase pointers.

    It works in your isolated example because auto in that case infers a Derived * type, so the appropriate destructor is called in this "static context".

    But in general, you should consider that according to the standard, deleting a base class pointer that has no virtual destructor is UNDEFINED BEHAVIOR. That means "anything" can happen, what usually ends up happening is calling the destructor for the base class type, which means for every other type in the hierarchy you are not getting the right destruction behavior.

    As for using MS's __interface - don't do it unless you really have to, then you won't have to deal with the limitation it imposes. I'd recommend you keep away from MS's language "extensions" and stick to good old standard and portable C++, which works with every compiler out there. There are no interfaces in C++ anyway.

    You should design your hierarchy so that you absolutely do have a virtual destructor, if not at the very bottom, then at least at the "base level" pointers you will be using. Either make IBase a class with virtual destructor, or make a class Base : public IBase that has one, and use that as your "polymorphic root".

    struct A {
      ~A() { std::cout << "destroying A" << std::endl; }
    };
    
    struct Base {
      ~Base() { std::cout << "destroying Base" << std::endl; }
    };
    
    struct Derived : Base {
      ~Derived() { std::cout << "destroying Derived" << std::endl; }
      A a;
    };
    

    And then:

      Base * p = new Derived;
      delete p; // destroying Base - bad bad, Derived is not destroyed, neither is A
    

    But it only takes the changing of ~Base() to be virtual and you get the expected output:

    destroying Derived
    destroying A
    destroying Base
    

    Also generally, you don't base (as in "use for foundation") classes on interfaces, just to extend them.

    The thing is that when you have a virtual destructor at the polymorphic level you are working on, it gets invoked from the vtable, meaning it will call the appropriate destructor for each unique type, since each unique type has its own dedicated vtable. If you don't have a virtual destructor, the standard says "undefined behavior" which leaves the choice to different compiler vendors to do as they please, and they end up doing the most logical thing - call the destructor for the type of the pointer. You couldn't ask for more... and yet it is not desirable, because you end up skipping all the destruction for every member or inherited type on top of the base type.