Leo*_*eon 2 c++ polymorphism overriding derived-class virtual-destructor
有一个虚拟类作为回调接口,我既不能修改,也不能要求作者修复。类的唯一成员是很多可以覆盖的虚方法,以便让库回调到我的代码中。为了获得一些回调机会,我应该为那个虚拟类创建一个派生类,并覆盖相应的虚拟方法。如果我对某些回调机会不感兴趣,我只需要避免覆盖它们。
但是,该接口类的声明有一个缺陷——它的析构函数没有声明为 virtual。
例如:
class callback_t {
public:
virtual void onData( int ) {};
};
Run Code Online (Sandbox Code Playgroud)
我创建了一个子类并且不覆盖析构函数,但是当我删除类的动态对象时child_t
,我遇到来自编译器的警告(gcc9 with C++17):
删除具有非虚拟析构函数的多态类类型“child_t”的对象可能会导致未定义的行为。
class child_t : public callback_t {
public:
~child_t() {
// release things specific to the child...
};
void onData( int ) override {
// do things I want when onData
};
private:
int m_data = 0;
};
int main() {
child_t* pc = new child_t;
// pass pc into the routines of the library
// working...
delete pc; /*deleting object of polymorphic class type ‘child_t’ which has non-virtual destructor might cause undefined behavior */
};
Run Code Online (Sandbox Code Playgroud)
问题:如何正确优雅地消除警告(我必须提交没有警告的代码)?
笔记和修改:
我不能修改类 callback_t 的声明,我也不能要求它的作者修复!这是一个权威机构发布的库。劝我改基类也没有用,判断lib的代码质量也没意义;
我从来不打算用基类类型的指针来释放child_t类的对象,我清楚地知道virtual-dtor和static-dtor之间的区别;
我需要解决的主要问题是消除编译警告,因为我确定没有内存泄漏,也没有遗漏恢复某些状态;
在这种情况下,基类中没有数据,没有有意义的代码,制作它的唯一目的是派生自,因此不应将其标记为 final。但是我尝试将 child_t 设为 ' final
',警告消失了。我不确定这个方法是否正确。如果是这样,我认为这是迄今为止最便宜的方法;
我还尝试将 child_t 的 dtor 设为virtual
,警告也消失了。但我仍然不确定它是否正确。
一virtual
,如果你需要时才需要析构函数delete
通过基类指针派生的对象。如果您不需要这样做,那么基类析构函数virtual
无关紧要的事实无关紧要(尽管这是一个相当大的暗示,该类从未打算继承自-在现代代码中,它可能应该是标记final
)。您仍然可以很好地从类中派生。您只需要注意派生类的对象是如何销毁的。
您收到的警告是误报。和
child_t* pc = new child_t;
// pass pc into the routines of the library
// working...
delete pc;
Run Code Online (Sandbox Code Playgroud)
pc
指向 a child_t
,它的静态类型是指向 a 的指针child_t
,因此将调用正确的析构函数。如果你有
callback_t* pc = new child_t;
// pass pc into the routines of the library
// working...
delete pc;
Run Code Online (Sandbox Code Playgroud)
然后警告将是正确的,因为只会callback_t
调用析构函数。
有一个解决方法,那就是使用std::shared_ptr
. 指针将正确的删除器存储在它的存储中,因此即使析构函数不是虚拟的,也会调用正确的派生析构函数而不是基函数。您可以在shared_ptr magic 中看到更多相关信息:)