缺少虚拟析构函数记忆效应

Missing Virtual Destructor Memory Effects

根据标准,缺少虚拟析构函数的多态性会导致未定义的行为。实际上,它确实导致派生 class 的析构函数在删除父 class 时未被调用。但是,它是否也导致任何常见的compilers/systems内存泄漏?我对 Android/Linux 上的 g++ 特别感兴趣。

具体来说,我指的是删除派生class的内存是否会以某种方式泄漏。考虑:

class Base {}
class Derived {
    int x;
}

如果我将 Base* 删除到 Derived,我会泄漏 4 个字节吗?或者内存分配器是否已经根据分配知道要释放多少字节?

当然可以。考虑:

class A
{
public:
 virtual void func() {}
};

class B : public A
{
public:
   void func() { s = "Some Long String xxxxxx"; }
private:
   std::string s;
   // destructor of B will call `std::string` destructor.
};

A* func(bool b)
{
   if (b)
       return new B;
   return new A;
}

... 
   A* a = func(true);
...
   delete a;

现在,这将造成内存泄漏,因为 B 对象中的 std::string s 未被 A::~A 释放 - 您需要调用 B::~B,这将只有当析构函数是虚拟的时才会发生。

请注意,这适用于我所知道的所有编译器和所有 运行 时间系统(这是所有常见的和一些不太常见的)。

编辑:

基于更新后的实际问题:内存取消分配是根据分配的大小发生的,因此如果您可以保证不会因为 construction/use 的 class 而发生单个分配],那么没有虚拟析构函数是安全的。然而,如果 base-class 的 "customer" 可以使 his/her 自己的扩展 classes,这会导致有趣的问题。将派生的 class 标记为 final 将防止它们被进一步派生,但如果基础 class 在其他人可以包含的头文件中可见,那么你 运行有人从 Base 中派生出自己的 class 的风险会做一些分配的事情。

所以,换句话说,在 PImpl 之类的东西中,Impl class 隐藏在一个没有其他人从中派生的源文件中,这似乎是合理的.对于大多数其他情况,这可能是个坏主意。

缺少析构函数会导致未定义的行为,特别是因为编译器不可能确切知道副作用可能是什么。

将其视为 RAII 的清理端。在那种情况下,如果您尽管声称自己清理了却设法不清理,副作用可能是:

  • 泄漏的内存(你分配了一些东西......你现在什么时候释放它?)
  • 死锁(你锁定了一些东西......你现在什么时候解锁?)
  • 套接字保持打开状态(您有时打开它...但现在什么时候关闭它?)
  • 文件保持打开状态(您有时打开它...但现在什么时候刷新它?)
  • 访问无效指针(例如,您更新了指向某个成员的指针...但现在什么时候取消设置它?)
  • 您的硬盘被清空(从技术上讲,这是 任何 未定义行为的有效答案)

这应该会导致未定义的行为,这意味着它也可能导致内存泄漏。在 5.3.5/3 (n4296 c++14) 中,delete 你有:

In the first alternative (delete object), if the static type of the object to be deleted is different from its dynamic type, the static type shall be a base class of the dynamic type of the object to be deleted and the static type shall have a virtual destructor or the behavior is undefined. In the second alternative (delete array) if the dynamic type of the object to be deleted differs from its static type, the behavior is undefined.