使用受保护的非虚拟析构函数时,禁止delete-non-virtual-dtor警告

时间:2017-04-07 16:17:08

标签: c++ g++

我有一个纯抽象接口类,以及一个实现接口的派生类。

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

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

我的界面类没有虚拟析构函数

尝试使用基类指针破坏派生实例显然是未定义的行为

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

幸运的是,我的编译器非常聪明,能够捕获这个(使用-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;
        ^

我可以通过确保不尝试使用基类指针

来删除这种未定义的行为
int main()
{
    Bar* b = new Bar;
    b->doStuff();
    delete b;
}

不幸的是,这个程序形成良好并且吐出类似错误并不够聪明

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;
     ^

有趣的是,它说可能导致未定义的行为,而

受保护的非虚拟析构函数:

one of Herb Sutter's Guru of the Week's中,他给出了以下建议:

  

准则#4:基类析构函数应该是公共的和虚拟的,或者是受保护的和非虚拟的。

所以让我的析构函数保护非虚拟化。

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

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

现在当我意外地尝试使用基类指针删除时,我得到另一个失败

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;
         ^

太好了,这给了我正在寻找的东西。让我们修复代码,这样我就不会使用基类指针删除

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

不幸的是我得到了和以前一样的错误

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;
         ^

问题:

我怎样才能充分利用两个世界?

  • 当我忘记创建受保护的非虚拟析构函数时,保持delete-non-virtual-dtor错误,并尝试通过基类指针删除
  • 当我使用受保护的非虚拟析构函数时,取消警告,并通过派生类指针删除

额外的超级奖金:

  • 当我忘记使用受保护的非虚拟析构函数时,取消警告,但我正在通过派生类指针正确删除

2 个答案:

答案 0 :(得分:1)

标记班级final会删除警告。

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

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

Demo

答案 1 :(得分:1)

编译器告诉您问题出在而不是 Foo 。如果你有另一个继承自Bar的类说Baz:

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

这可能导致未定义的行为,例如案例

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

因为中的析构函数不是虚拟的。因此,解决方案是将Bar标记为已建议的最终版,或者将Bar中的析构函数设置为虚拟(因为它是公开的)或根据Herb的建议对其进行保护。