来自类实现接口的析构函数在引用为接口时未调用

时间:2015-03-23 14:39:57

标签: c++ c++11 interface virtual destructor

我有一个基本界面(在Visual Studio 2013中使用Microsofts C ++语法),它公开了这样的简单函数:

__interface IDisposable {
    void Dispose();
};
__interface IBase : public IDisposable {
    void Foo();
    void Bar();
};

有一种特定类型的类必须继承这些方法,因此这些类具有以下结构:

class Derived : public IBase {
public:
    Derived();
    ~Derived();
    void Dispose();
    void Foo();
    void Bar();
}

这些类也有一些变量,我想使用智能指针,但这些指针确实产生泄漏,因为在以下场景中没有调用析构函数~Derived()

  • 我有Stack<IBase*>来处理这些对象(例如DerivedDerived2等)。
  • 应该使用相应的数据(pCurrent)从堆栈中删除最顶层的元素(m_Data)后,我正在调用处理资源的方法:((IDisposable*)pCurrent->m_Data)->Dispose();
  • 此调用之后是delete pCurrent->m_Data,显然会尝试调用~IBase(),因为它是一个接口,因此显然不存在。因此,所提到的~Derived()也不会被调用,Derived中的任何智能指针也不会被删除,这会导致严重的内存泄漏。

令人惊讶的是手动删除工作:

auto p = new Derived();
delete p; // ~Derived() is properly called as we are not handling a IBase* object

我在考虑使用虚拟析构函数,但是没有办法在这些接口中定义析构函数以使~Derived()成为虚拟析构函数。

是否有更好的方法可以调用正确的析构函数?

2 个答案:

答案 0 :(得分:5)

我在评论中提到的问题是,在多态层次结构的底部没有虚拟析构函数,因此当您删除IBase指针时会出现未定义的行为。

它适用于您的孤立示例,因为在这种情况下auto推断出Derived *类型,因此在此“静态上下文”中调用相应的析构函数。

但总的来说,你应该考虑根据标准,删除没有虚析构函数的基类指针是UNDEFINED BEHAVIOR。这意味着“任何事情”都可能发生,通常最终发生的事情是调用基类类型的析构函数,这意味着对于层次结构中的每个其他类型,您都没有获得正确的破坏行为。

至于使用MS的__interface - 除非你真的必须这样做,否则你不必处理它所施加的限制。我建议你远离MS的语言“扩展”,并坚持使用旧的标准和可移植的C ++,它适用于那里的每个编译器。无论如何,C ++中没有接口。

你应该设计你的层次结构,这样你绝对有一个虚拟析构函数,如果不是在最底层,那么至少在你将使用的“基础级别”指针。使用虚拟析构函数创建IBase一个class,或者创建一个具有一个的class Base : public IBase,并将其用作“多态根”。

struct A {
  ~A() { std::cout << "destroying A" << std::endl; }
};

struct Base {
  ~Base() { std::cout << "destroying Base" << std::endl; }
};

struct Derived : Base {
  ~Derived() { std::cout << "destroying Derived" << std::endl; }
  A a;
};

然后:

  Base * p = new Derived;
  delete p; // destroying Base - bad bad, Derived is not destroyed, neither is A

但它只需要将~Base()更改为虚拟并获得预期的输出:

destroying Derived
destroying A
destroying Base

通常,您不会在接口上建立类(如“用于基础”),只是为了扩展它们。

问题是当你正在处理的多态级别上有一个虚拟析构函数时,它会从vtable调用,这意味着它会为每个唯一类型调用相应的析构函数,因为每个唯一类型都有自己的专用vtable 。如果你没有虚拟析构函数,那么标准会说“未定义的行为”会让不同的编译器供应商选择自己做的事情,并且他们最终做了最合乎逻辑的事情 - 调用析构函数来获取指针的类型。你不能要求更多......然而这是不可取的,因为你最终会在基础类型之上跳过每个成员或继承类型的所有破坏。

答案 1 :(得分:0)

执行此操作的常用方法是使您调用delete的任何类型都具有virtual析构函数,如:

virtual ~IDisposable() = default;

由于C ++的规则,所有派生类也将具有virtual析构函数。

我的建议是将结果存储在unique_ptr中,这样您就不必自己致电delete,如:

std::unique_ptr<IDisposable> p(new Derived());

一种不那么具有干扰性的方法是,不是声明析构函数virtual,而是使用shared_ptr来保存对象,因为它使用不同的方法(模板化构造函数和类型擦除来跟踪正确调用析构函数的实际对象类型,如:

std::shared_ptr<IDisposable> p = std::make_shared<Derived>();

由于我不确定__interface关键字的作用(特定于Microsoft),您可能需要这样做,但virtual析构函数使用unique_ptr解决方案是优选的,因为它不会在混合中添加引用计数。