如何从没有虚拟析构函数的类派生?

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)

问题:如何正确优雅地消除警告(我必须提交没有警告的代码)?

笔记和修改:

  1. 我不能修改类 callback_t 的声明,我也不能要求它的作者修复!这是一个权威机构发布的库。劝我改基类也没有用,判断lib的代码质量也没意义;

  2. 我从来不打算用基类类型的指针来释放child_t类的对象,我清楚地知道virtual-dtor和static-dtor之间的区别;

  3. 我需要解决的主要问题是消除编译警告,因为我确定没有内存泄漏,也没有遗漏恢复某些状态;

  4. 在这种情况下,基类中没有数据,没有有意义的代码,制作它的唯一目的是派生自,因此不应将其标记为 final。但是我尝试将 child_t 设为 ' final',警告消失了。我不确定这个方法是否正确。如果是这样,我认为这是迄今为止最便宜的方法;

  5. 我还尝试将 child_t 的 dtor 设为virtual,警告也消失了。但我仍然不确定它是否正确。

Jes*_*uhl 7

virtual,如果你需要时才需要析构函数delete通过基类指针派生的对象。如果您不需要这样做,那么基类析构函数virtual无关紧要的事实无关紧要(尽管这是一个相当大的暗示,该类从未打算继承自-在现代代码中,它可能应该是标记final)。您仍然可以很好地从类中派生。您只需要注意派生类的对象是如何销毁的。


Nat*_*ica 7

您收到的警告是误报。和

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 中看到更多相关信息:)