虚拟析构函数和内存释放
Virtual destructor and memory deallocation
我不太确定我是否理解虚拟析构函数和在堆右侧分配空间的概念。让我们看看下面的例子:
class Base
{
public:
int a;
};
class Derived : public Base
{
public:
int b;
};
我想如果我做这样的
Base *o = new Derived;
堆上分配了8个字节(或系统上需要的任何两个整数),看起来如下:…|a|b|。。。
现在,如果我这样做:
delete o;
"delete"如何知道,为了从堆中删除所有内容,实际上是哪种类型的o?我想它必须假设它是Base类型,因此只从堆中删除a(因为它不能确定b是否属于对象o):…|b|。。。
b将保留在堆上并且不可访问。
执行以下操作:
Base *o = new Derived;
delete o;
真的会引发内存泄漏,我需要一个虚拟析构函数吗?或者delete知道o实际上是派生类的,而不是基类的吗?如果是的话,这是怎么回事?
谢谢大家。:)
您对实现做了很多假设,这可能或者可以不保持。在delete
表达式中,动态类型必须是与静态类型相同,除非静态类型具有虚拟析构函数。否则,它就是未定义的行为。时期那是你所要知道的一切;我在实现中使用过否则它就会崩溃,至少在某些情况下是这样;我用过这样做会破坏自由空间领域的实现,因此代码稍后会崩溃,变成一个完全无关的部分的代码。(记录在案,VC++和g++都属于第二种情况,分别为至少在使用已发布代码的常用选项进行编译时是这样。)
首先,您在示例中声明的类具有琐碎的内部结构。从纯粹的实用角度来看,为了正确地销毁此类的对象,运行时代码不需要知道被删除对象的实际类型。它只需要知道要释放的内存块的正确大小。这实际上是C样式库函数(如malloc
和free
)已经实现的。正如您可能知道的,free
隐式地"知道"要释放多少内存。你上面的例子没有涉及到除此之外的任何内容。换句话说,您上面的例子不够详细,无法真正说明任何特定于C++的东西。
然而,从形式上讲,您的示例的行为是未定义的,因为C++语言在形式上需要虚拟析构函数来进行多态删除,而不管类的内部结构有多琐碎。因此,您的"delete
如何知道…"问题根本不适用。您的代码已损坏。它不起作用。
其次,当您开始要求对类进行非平凡的销毁时,实际的、具体的C++效果开始出现:通过为析构函数定义显式主体,或者通过向类添加非平凡的成员子对象。例如,如果将std::vector
成员添加到派生类,则派生类的析构函数将负责该子对象的(隐式)销毁。为了实现这一点,您必须声明您的析构函数virtual
。通过与调用任何其他虚拟函数相同的机制来调用适当的虚拟析构函数。这基本上就是你的问题的答案:运行时代码并不关心对象的实际类型,因为普通的虚拟调度机制将确保调用正确的析构函数(就像它与任何其他虚拟函数一起工作一样)。
第三,当您为类定义专用的operator delete
函数时,虚拟销毁的另一个显著效果就会出现。语言规范要求选择正确的operator delete
函数,就好像它是从要删除的类的析构函数内部查找的一样。许多实现实际上实现了这一要求:它们实际上从类析构函数内部隐式调用operator delete
。为了使该机制正常工作,析构函数必须是虚拟的。
第四,你问题的一部分似乎表明,你认为如果不能定义虚拟析构函数,就会导致"内存泄漏"。这是一个流行的,但完全不正确和完全无用的城市传说,由低质量的来源延续。在没有虚拟析构函数的类上执行多态删除会导致未定义的行为,并导致完全不可预测的破坏性后果,而不是一些"内存泄漏"。在这种情况下,"内存泄漏"不是问题所在。
被删除对象的大小没有问题-这是已知的。虚拟析构函数解决的问题可以演示如下:
class Base
{
public:
Base() { x = new char[1]; }
/*virtual*/ ~Base() { delete [] x; }
private:
char* x;
};
class Derived : public Base
{
public:
Derived() { y = new char[1]; }
~Derived() { delete [] y;}
private:
char* y;
};
然后拥有:
Derived* d = new Derived();
Base* b = new Derived();
delete d; // OK
delete b; // will only call Base::~Base, and not Derived::~Derived
第二次删除将无法正确完成对象。如果virtual
关键字被取消注释,那么第二个delete
语句将按预期运行,它将与Base::~Base
一起调用Derived::~Derived
。
正如评论中所指出的,严格地说,第二次删除会产生未定义的行为,但这里使用它只是为了强调虚拟析构函数。
- 如何在c++中释放内存
- 为什么这个 std::queue/指向结构的指针列表直到 List.Size() == 0 才释放内存?
- std::unordered_map析构函数不释放内存?
- 在C++中释放内存期间,迭代器与指针有何不同
- 使用共享指针时,从共享指针本身释放内存的机制是什么
- 释放内存(主题模板)时出现问题
- 使用后自动释放内存
- C++ 如何释放内存
- 从函数内对象的向量中释放内存
- C++ 在不释放内存的情况下调用析构函数
- 多个线程之间的获取-释放内存顺序
- C++ - 析构函数只是释放内存还是实际删除对象
- 使用 RAII 替换最终块以释放内存
- 如何通过带有指向基类的指针的删除运算符释放内存
- 代码中的"sprintf"用法是否需要释放内存?
- C++何时使用 delete[] 并正确释放内存?
- 为什么此获取和释放内存围栏不能给出一致的值?
- 抛出新表达式的参数子表达式时释放内存
- 在这种情况下,如何释放内存?
- 在 c++ 中应按什么顺序释放内存?