Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

g++

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
like image 220
Steve Lorimer Avatar asked Apr 07 '17 16:04

Steve Lorimer


1 Answers

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.

like image 63
schrödinbug Avatar answered Oct 01 '22 19:10

schrödinbug