Steve Lorimer
Steve Lorimer

Reputation: 28659

Suppress delete-non-virtual-dtor warning when using a protected non-virtual destructor

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?

Super awesome bonus extra:

Upvotes: 15

Views: 10869

Answers (3)

NutCracker
NutCracker

Reputation: 12263

If your Bar class is something you can't change, you can do the following:

struct BarFinal final : Bar {};

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

Upvotes: 0

schrödinbug
schrödinbug

Reputation: 863

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.

Upvotes: 8

Jarod42
Jarod42

Reputation: 217255

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

Upvotes: 8

Related Questions