6

几个小时前,我一直在摆弄一个内存泄漏问题,结果我真的弄错了一些关于虚拟析构函数的基本知识!让我解释一下我的班级设计。

class Base
{
  virtual push_elements()
  {}
};

class Derived:public Base
{
vector<int> x;
public:
   void push_elements(){ 
      for(int i=0;i <5;i++)
         x.push_back(i); 
   }
};

void main()
{
    Base* b = new Derived();
    b->push_elements();
    delete b;
}

边界检查器工具报告了派生类向量中的内存泄漏。而且我发现析构函数不是虚拟的,并且没有调用派生类析构函数。当我将析构函数设为虚拟时,它出人意料地得到了修复。即使没有调用派生类析构函数,向量是否也不会自动释放?这是 BoundsChecker 工具中的一个怪癖还是我对虚拟析构函数的理解是错误的?

4

8 回答 8

15

当基类没有虚拟析构函数时,通过基类指针删除派生类对象会导致未定义的行为。

您所观察到的(对象的派生类部分永远不会被破坏,因此它的成员永远不会被释放)可能是许多可能行为中最常见的,并且是一个很好的例子,说明为什么确保您的析构函数是重要的以这种方式使用多态性时是虚拟的。

于 2010-04-27T15:34:02.530 回答
8

如果基类没有虚拟析构函数,那么代码的结果是未定义的行为,不一定是调用了错误的析构函数。这大概是 BoundsChecker 正在诊断的内容。

于 2010-04-27T15:34:36.187 回答
2

尽管这在技术上是不确定的,但您仍然需要知道最常见的故障方法才能进行诊断。常见的失败方法是调用错误的析构函数。我不知道任何会以任何其他方式失败的实现,尽管我承认我只使用了两个实现。

发生这种情况的原因与当您尝试覆盖非虚拟成员函数并通过基指针调用它时调用“错误”函数的原因相同。

于 2010-04-27T15:46:09.770 回答
1

如果析构函数不是虚拟的,则将调用 Base 析构函数。基础析构函数清理基础对象并完成。基对象析构函数无法知道派生对象,它必须是调用的派生析构函数,而与任何函数一样,做到这一点的方法是使析构函数成为虚拟的。

于 2010-04-27T15:32:53.897 回答
1

From the C++ FAQ Lite: "When should my destructor be virtual?" Read it here. (C++ FAQ Lite is an excellent source for all your questions related to C++, by the way).

于 2010-04-27T16:17:13.150 回答
1

在 C++ 中,平凡析构函数是一个递归定义的概念——它是当类的每个成员(以及每个基类)都有一个平凡析构函数时编译器为您编写的析构函数。(有一个类似的概念称为平凡构造函数。)

当具有非平凡析构函数的对象包含在对象中时(如vector您的示例中的),则外部对象(如您的Derived)的析构函数不再是微不足道的。即使您没有编写析构函数,C++ 编译器也会自动编写一个析构函数,该析构函数调用任何具有析构函数的成员的析构函数。

因此,即使您没有编写任何内容,编写非虚拟析构函数的注意事项仍然适用。

于 2010-04-27T16:17:45.553 回答
1

如果您来自 c#,那么您想知道为什么向量不会自动取消分配是正确的。但是在 c++ 中,除非您使用 Microsoft Manged Extesions to C++ (C++/CLI),否则自动内存管理不可用。

由于基类中没有虚拟的析构函数,派生类对象将永远不会被释放,因此您会泄漏为派生类的向量数据成员分配的内存。

于 2010-11-06T00:11:05.650 回答
0

析构函数是类的成员函数,其名称与类名相同,并且前面有波浪号(~)。析构函数用于在对象超出范围时销毁类的对象,或者您可以说所有类销毁的清理都在析构函数中完成。当对象超出范围时,在构造类中的对象期间分配的所有内存都会被破坏(或内存释放)。

通过BoundsCheck示例查找更多详细信息

于 2010-06-29T18:32:11.513 回答