int main()
{
Base *p = new Derived;
delete p;
return 0;
}
我有些困惑,为什么在这里删除p不会删除派生对象?是这样派生对象包含基类型的子对象,而指针p
实际上指向派生对象的基础部分(子对象)。因此,当delete p
运行时,它将只能删除派生类对象的基类部分,并且它将具有导出部件销毁的未定义行为。这将导致内存泄漏。因此,为了避免内存泄漏,我们需要设法调用对象的基础和派生析构函数以避免内存泄漏!是这样吗?这是我们需要虚拟析构函数的原因吗?因此,当基础析构函数是虚拟的时,派生类析构函数将覆盖基类的析构函数(它是虚拟的)。请澄清??
答案 0 :(得分:3)
您应该声明一个虚拟析构函数:
class Base {
//etc...
virtual ~Base();
};
class Derived {
//etc...
virtual ~Derived();
};
Base* p = new Derived();
delete p;
(当然上面遗漏了很多东西,包括构造函数)
答案 1 :(得分:3)
如果你在询问基类没有虚拟析构函数时的行为,那么你的混淆就是因为你已经对这个delete
表达式的行为有一些预先设想的错误概念。
“它只能删除对象的基类部分”
“这会导致内存泄漏”
这些都没有任何意义。这里没有内存泄漏,并且没有任何确定性能够“删除它的基类部分”。
如果基类没有虚析构函数,则此类代码的行为只是 undefined 。未定义的行为可以表现出很多种不同的方式,包括但不限于不正确的析构函数调用,不正确的operator delete
选择,堆损坏,以及是“内存泄漏”,包括直接和间接的。在这种情况下,有很多不同的东西可以搞砸。不只是一些“内存泄漏”,因为流行的误解让人们相信。 (关于“内存泄漏”的流行内容来自哪里。有人知道吗?)
所以,你确实需要虚拟析构函数。如果一个人决定全力以赴地详细分析它,那么你需要的完整原因列表可能会很长。但无论如何,这是一个实现细节。如果不将它与特定的实现联系起来,没有具体的解释。
至于“概念”解释......总是最明显的一个:当然,为了执行适当的破坏,必须调用正确的析构函数。即使我们只是考虑用户定义的销毁步骤(即用户在派生类析构函数中明确写出的内容),我们仍然需要析构函数多态来正确调用那个析构函数。
但是,还有许多其他内部原因。例如,在典型实现中,对原始内存释放的正确operator delete
的选择也会依赖于析构函数的虚拟性(例如,参见here)
答案 2 :(得分:1)
除非基类具有虚拟析构函数,否则通过指向其基类的指针删除对象是未定义的。即使使用虚拟析构函数,删除的顺序也可能不会立即浮现在脑海中。
有关详细信息,请参阅this destructor reference。
答案 3 :(得分:1)
一方面有很多问题,不幸的是C ++语法具有误导性,所以让我们回顾一下我们的基础。
析构函数会发生什么?
当一个对象被破坏时,语言会调用它的析构函数;按顺序发生以下事件序列:
注意:virtual
基数在属性之前被破坏,但是在执行主体之后。
重要的一点是,即使Derived
方法隐藏了它的Base
对应方(如果有的话),在构造函数和析构函数的情况下,基本对应方会自动为您调用你无法控制的定义明确的点。
virtual
析构函数是什么?
当Base
具有virtual
析构函数时,Derived
类中隐式声明或用户声明的析构函数自然会覆盖它。与其他virtual
方法类似,它意味着当析构函数被调用 unqualified (即,不在b.Base::foo()
中)时,调用实际上被分派到 final -overider ,它是派生最多的对象的析构函数(对象的真实动态类型)。
然而,正如前面所述,这并不意味着Base
析构函数本身永远不会运行,因为析构函数是特殊的;您可以将Derived
析构函数视为(自动)实现为:
Derived::~Derived(): ~Base(), ~attr0(), ~attr1() { ... }
代码从右向左执行。
delete
上的Base*
表达式是什么?
好吧,很多人会认为Base* b = ...; delete b;
desugared :
// NOT QUITE
Base* b = ...;
b.~Base(); // possibly virtual destructor
operator delete(&b);
然而这实际上是不正确的。问题在于:
Derived
对象地址可能与其Base
子对象不同,但必须使用operator delete
返回的确切指针值调用operator new
。 因此,编译器需要实现一些魔力;这取决于他们。例如,实现Itanium ABI(gcc,icc,clang,...)的编译器将向v表添加一个特殊条目,该条目包含一个魔术函数,该函数在调用最派生对象的析构函数并调用之前执行指针调整operator delete
地址正确。它可以被视为:
operator delete
那么,如果
class Derived: public Base { public: virtual ~Derived() override {} // FOR ILLUSTRATION PURPOSE ONLY // DON'T DO THIS AT HOME: // - you are forbidden to use `__` in your identifiers // - you are forbidden to call `delete this;` or any similar statement // FOR ILLUSTRATION PURPOSE ONLY virtual void __automagic_delete() { this->Derived::~Derived(); operator delete(this); } };
不是Base::~Base
怎么办?
嗯,正式来说,这是未定义的行为。
在实践中,经常出现两个问题:
virtual
,因此不会释放其属性或其他基类所拥有的任何资源。这可能导致内存泄漏,文件描述符泄漏,连接器泄漏,死锁,...... Derived::~Derived
对象的地址与其Derived
子对象的地址不同,则调用Base
的地址不正确......严格执行后会立即导致{{1}并且在不太严格的情况下可能会导致内存损坏但当然,由于这是未定义的行为,所以任何事情都可能发生,所以这只是冰山一角。