【问题标题】:Destructor from class implementing interface not called when referencing as interface引用为接口时未调用实现接口的类的析构函数
【发布时间】:2015-05-26 14:39:01
【问题描述】:

我有一个基本接口(在 Visual Studio 2013 中对接口使用 Microsoft 的 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() 也成为虚拟。

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

【问题讨论】:

  • 你试过虚拟析构函数吗?
  • @ddriver 即使使用virtual ~Derived(),在所描述的场景中也不会调用此函数。
  • 您应该从多态层次结构的最底部以及接口进行虚拟破坏。
  • @ddriver 这也让我想到了,但documentation 提到接口不能包含构造函数、析构函数或运算符。这是否意味着我必须重新考虑层次结构并用纯虚函数制作抽象基类,而不是使用__interface关键字来简化代码?
  • 当然可以,C++ 中没有接口之类的东西,你应该放弃那个关键字,那就是 MS BS。它只是类,因此它们可以同时具有构造函数和析构函数,即使它们的某些方法是抽象的,因此实际的“接口”类也是抽象的。

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


【解决方案1】:

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

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

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

至于使用 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 .如果您没有虚拟析构函数,则标准会说“未定义的行为”,这让不同的编译器供应商可以随意选择,他们最终会做最合乎逻辑的事情 - 调用析构函数以获取指针的类型.您不能要求更多...但这并不可取,因为您最终会跳过基类型之上的每个成员或继承类型的所有销毁。

【讨论】:

    【解决方案2】:

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

    virtual ~IDisposable() = default;
    

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

    我的建议是将结果也存储在unique_ptr 中,这样您就不必自己调用delete,如下所示:

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

    侵入性较小的方法是,您可以使用shared_ptr 来保存对象,而不是声明析构函数virtual,因为它使用不同的方法(模板构造函数和类型擦除来跟踪实际对象类型) 来正确调用析构函数,如:

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

    由于我不确定 __interface 关键字的作用(这是 Microsoft 特有的),您可能需要这样做,但最好使用带有 unique_ptr 解决方案的 virtual 析构函数,因为那样不会'不要将引用计数添加到组合中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-11
      • 1970-01-01
      • 2014-06-17
      • 2020-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多