10
votes

I have a pure abstract interface class, and a derived class which implements the interface.

struct Foo
{
    virtual void doStuff() = 0;
};

struct Bar : Foo
{
    void doStuff() override { }
};

My interface class doesn't have a virtual destructor.

Attempting to destruct a derived instance using a base class pointer is obviously therefore undefined behaviour

int main()
{
    Foo* f = new Bar;
    f->doStuff();
    delete f;
}

Luckily my compiler is clever enough to catch this (with -Werror)

main.cc:15:9: error: deleting object of abstract class type ‘Foo’ which has
    non-virtual destructor will cause undefined behaviour [-Werror=delete-non-virtual-dtor]
 delete f;
        ^

I can avoid this undefined behaviour by ensuring I don't attempt to delete using a base class pointer

int main()
{
    Bar* b = new Bar;
    b->doStuff();
    delete b;
}

Unfortunately it's not clever enough to pick up that this program is well formed, and spits out a similar error

main.cc:15:9: error: deleting object of polymorphic class type ‘Bar’ which has 
    non-virtual destructor might cause undefined behaviour [-Werror=delete-non-virtual-dtor]
  delete b;
     ^

Interestingly it says might cause undefined behaviour, not will

Protected non-virtual destructor:

In one of Herb Sutter's Guru of the Week's he gives the following advice:

Guideline #4: A base class destructor should be either public and virtual, or protected and nonvirtual.

So lets make my destructor protected nonvirtual.

struct Foo
{
    virtual void doStuff() = 0;
protected:
    ~Foo() = default;
};

struct Bar : Foo
{
    void doStuff() override { }
};

Now when I accidentally try to delete using a base class pointer I get another failure

int main()
{
    Foo* f = new Bar;
    f->doStuff();
    delete f;
}
main.cc:5:2: error: ‘Foo::~Foo()’ is protected
  ~Foo() = default;
  ^
main.cc:17:9: error: within this context
  delete f;
         ^

Great, that gives me what I was looking for. Let's fix the code so I don't delete using a base class pointer

int main()
{
    Bar* b = new Bar;
    b->doStuff();
    delete b;
}

Unfortunately I get the same error as before

main.cc:17:9: error: deleting object of polymorphic class type ‘Bar’ which has 
non-virtual destructor might cause undefined behaviour [-Werror=delete-non-virtual-dtor]
  delete b;
         ^

Question:

How can I get the best of both worlds?

  • Keep the delete-non-virtual-dtor error for when I forget to create a protected non-virtual destructor, and I try delete through a base-class pointer
  • Suppress the warning when I use a protected non-virtual destructor, and I delete through a derived-class pointer

Super awesome bonus extra:

  • Suppress the warning when I forget to use a protected non-virtual destructor, but I am correctly deleting through a derived-class pointer
2
Can you mark Bar as final class ?Jarod42
The warnings seem to be compiler dependent. You may want to add the compiler tag(s) to the post.R Sahu
@Jarod42 I could for some of the classes in my heirarchy, but unfortunately not for allSteve Lorimer
BTW, why not using virtual destructor ?Jarod42
I don't have warning neither when using std::unique_ptr/std::shared_ptr. Demo, but it might be wrong for unique_ptr :-(.Jarod42

2 Answers

5
votes

Marking the class final remove the warning.

struct Bar final : Foo
{
    void doStuff() override { }
};

int main()
{
    Bar* f = new Bar;
    f->doStuff();
    delete f;
}

Demo

4
votes

The compiler is telling you that the problem is in Bar not in Foo. If you were to have another class that inherits from Bar say Baz:

struct Baz : public Bar
{
  void doStuff() override { }
};

This could lead to undefined behavior such as the case

int main()
{
    Bar* bar_ptr = new Baz();
    bar_ptr->do_stuff();
    delete bar_ptr; // uh-oh! this is bad!
}

because the destructor in Bar is not virtual. So the solution is to mark Bar as final as has been suggested, or make the destructor in Bar virtual (since it's public) or make it protected in accordance with Herb's suggestions.