4

I found a memory leak in my code that was caused by calling only the base class destructor for objects. This problem is understood: I already added the virtual to the destructor of the interface class MyÌnterface. What puzzles me is that the compiler obviously created a standard destructor for my helper class MyHelperthat is eventually called. I tried this with two different compilers.

This was very surprising to me, since I observed that most default implementations are not created if members or base classes introduce restrictions. Why is the protection of the destructor not inherited?

#include <iostream>

class MyInterface
{
public:
    virtual void doSomethingUseful()=0;
    // a lot more functions declared omitted
    virtual void doSomethingElse()=0;
    virtual void doSomethingIndividual()=0;
protected:
    /// protected destructor to forbid calling it on interfaces
    ~MyInterface() {} // HERE the virtual is clearly missing
};

/// a common base that defaults most functions implementations
class MyHelper: public MyInterface
{
public:
    void doSomethingUseful() {}
    // a lot more default implementations omitted
    void doSomethingElse() {}
};

class SomeImplementation: public MyHelper
{
public:
    SomeImplementation()
    {
        std::cout << "SomeImplementation ctr" << std::endl;
    }
    ~SomeImplementation()
    {
        std::cout << "SomeImplementation dtr" << std::endl;
    }
    void doSomethingIndividual()
    {
        std::cout << "SomeImplementation did it." << std::endl;
    }
};

/// user of MyInterface cannot delete object mi passed as parameter
int deleteSafeUsage(MyInterface& mi)
{
    mi.doSomethingIndividual();
    // would cause a compiler error: delete &mi;
}

/// usage restricted to MyHelper level, only exception is object creation
int testIt()
{
    MyHelper* h = new SomeImplementation;
    deleteSafeUsage(*h);
    delete h; // <- HERE the memory leak happens!
}

Here the output of the above example code, which "shows" the missing SomeImplementation ctr:

SomeImplementation ctr
SomeImplementation did it.
Wolf
  • 9,679
  • 7
  • 62
  • 108
  • How do you know `MyHelper`'s d-tor got called? And can you please clarify what you mean by this: *most default implementations are not created if members or base classes introduce restrictions*? – jrok Feb 24 '14 at 10:41
  • @jrok try to uncomment the second line in `deleteSafeUsage()`, this will not compile. – Wolf Feb 24 '14 at 10:44
  • @jrok (2) For example: compilers don't create constructors, if members are references or const or base class constructors take parameters. – Wolf Feb 24 '14 at 10:46
  • (1) It does not compile because the destructor of `MyInterface` is protected. That proves nothing about the destructor of `MyHelper`. (2) None of the restrictions you mention apply in your example. About destructors - they're always generated (if not user declared), possibly as deleted, though. – jrok Feb 24 '14 at 10:49
  • But if what's troubling you is that `MyInterface`s destructor is `protected` - that's ok, it's still accesible from derived classes. – jrok Feb 24 '14 at 10:53
  • @jrok (2) This may be the answer: *destructors are always generated* where can I read that? (1) At least `~SomeImplementation` isn't called at the end of `testIt()`. The destructor of 'MyHelper` is empty so it will certainly be optimized. – Wolf Feb 24 '14 at 10:55

1 Answers1

5

Constructors and destructors are not inherited. So why would their visibility be inherited?

You may want to check standard to be sure, but cppreference says this, emphasis mine:

If no user-defined destructor is provided for a class type (struct, class, or union), the compiler will always declare a destructor as an inline public member of its class.

So, if you want ~MyHelper to be protected, you must declare it explicitly.

Note, that if MyInterface had a virtual destructor, the implicit destructor of MyHelper would also be virtual. So this aspect is inherited, sort of. Again, you'll want to consult standard if you want to be sure, but this is mentioned in c++ faq lite

For completeness, here's Herb Sutters guidelines on how to use virtuality in general and with destructors.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
  • This makes sense, but if I read the [following section](http://en.cppreference.com/w/cpp/language/destructor#Deleted_implicitly-declared_copy_destructor), it seems dangerous to me to remember this simple rule that public default destructors are always generated. – Wolf Feb 24 '14 at 11:09
  • True. It would be smart to always declare and properly define your destructors for non-pod classes. You'd also avoid this visibility surprise. – eerorika Feb 24 '14 at 11:17
  • Would you agree that letting `~MyInterface` non-virtual and adding a virtual destructor to `MyHelper` would also be an option? (I indeed use both inherited levels for my `SomeClasses`) – Wolf Feb 24 '14 at 11:28
  • In my opinion, all abstract classes should have virtual, public, defined destructors. That way you can easily destroy derived objects through interface without leaking memory. – eerorika Feb 24 '14 at 11:33
  • That means: no more *completely abstract* base classes (==interfaces) in C++? – Wolf Feb 24 '14 at 11:36
  • I suppose. What would be the advantage of *completely abstract base class* as opposed to *completely abstract base class except for the destructor*? – eerorika Feb 24 '14 at 11:42
  • Nothing. The problem arose from my wish to prohibit deleting interfaces by mistake. My first version of this interface did not even contain a destructor declaration. And so the compiler built one, which I didn't like. I'd find **interface declaration in C++** more clear, if there was only **one public section**. But it's C++, and so there will be some (good?) reason for the ability to call a destructor for an object of an abstract class. – Wolf Feb 24 '14 at 11:53
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/48257/discussion-between-user2079303-and-wolf) – eerorika Feb 24 '14 at 11:54