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

mwkjh3gx  于 2023-04-01  发布在  其他
关注(0)|答案(3)|浏览(151)

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

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:基类析构函数应该是public和virtual的,或者是protected和nonvirtual的。
所以让我的析构函数保护非虚拟的。

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错误
  • 当我使用一个受保护的非虚析构函数,并且我通过一个派生类指针删除时,禁止显示警告

超级棒的额外奖励:

  • 当我忘记使用受保护的非虚析构函数,但通过派生类指针正确删除时,禁止显示警告
zf9nrax1

zf9nrax11#

编译器告诉你问题出在Bar而不是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标记为final,或者将Bar中的析构函数设为虚的(因为它是公共的),或者按照Herb的建议将其设为受保护的。

gstyhher

gstyhher2#

标记类final删除警告。

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

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

Demo

2ic8powd

2ic8powd3#

如果你的Bar类是你不能改变的,你可以做以下事情:

struct BarFinal final : Bar {};

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

相关问题