一、为什么会考虑这个问题
假设框架提供一种机制,这种机制对于处理之后的所有对象都会执行释放(delete)操作,但是对于一些存在于内存池中的结构,如果使用默认delete操作就会有问题,因为默认的delete操作是把指定内存当做堆中空间操作的,所以对于位于内存池中的类型就需要考虑是不是需要重载自己的析构函数。
网站上对于这种结构的说明,其中大致的意思是delete操作符天生是虚函数,也就是执行析构函数的时候调用的是最底层的delete函数。那么这个功能是如何实现的?
二、编译器面临的问题
当delete一个包含了虚函数的类型的指针时,按照C++规范,需要同时执行派生类的析构函数。查看gcc为类型生成的虚函数表(或者通过反汇编,其实gdb调试时偶尔也可以遇到)可以看到,编译器为类型生成了两个虚函数,一个是用户定义的虚函数,另一个就是系统自动生成的析构函数,通过调试信息可以看到,这个自动生成的析构函数还有一个额外的__in_chrg参数,从参数名字上看是in charge的缩写,但是反汇编代码中其实并没有看到使用这个参数,这些细节由于不是很熟悉也不是很必要就不再展开了。
这里只是简单说明下这个析构函数存在的意义及主要功能。它的主要功能有两个,一个是对于虚继承的情况,虽然一个对象看到有一个父类,但是在多重+虚继承的情况下,这个父类可能是共享的,如果两个派生类同时执行父类的析构函数那么就会存在多次析构的问题;另一个就是这里要讨论的动态内存释放问题。
如果输入参数中的bit位置位,那么该函数不仅要进行析构函数,而且在析构之后还需要执行该对象内存的delete操作,所以这个__in_chrg 就是是否管理内存的意思。
三、为什么要把delete通过特定的析构函数实现呢
按照前面的讨论,一方面是为了和虚继承的代码复用,另一方面如果在调用的地方执行delete明显容易代码比较多,所以此时定义为一个约定好的析构函数看起来是一个比较合理的实现方法。
四、具体分析
关于这一点,在Stack Overflow上也有讨论,里面提到了重要的一点:So, operator delete, which can be implemented as a static member function should behave as if it were a virtual function.
1、简单测试代码
tsecer@harry: cat delete.virtual.dtor.cpp
struct A
{
virtual ~A(){}
};
struct B: public A
{
int x;
virtual ~B()
{
x = 1122;
}
};
int main()
{
A *pb = new B;
delete pb;
B b;
return 0;
}
2、gcc中对于这些特殊析构函数的mangle
gcc-4.8.2gcccpmangle.c
/* Handle destructor productions of non-terminal <special-name>.
DTOR is a destructor FUNCTION_DECL.
<special-name> ::= D0 # deleting (in-charge) destructor
::= D1 # complete object (in-charge) destructor
::= D2 # base object (not-in-charge) destructor
We also need to provide mangled names for the maybe-incharge
destructor, so we treat it here too. mangle_decl_string will
append *INTERNAL* to that, to make sure we never emit it. */
static void
write_special_name_destructor (const tree dtor)
{
if (DECL_DELETING_DESTRUCTOR_P (dtor))
write_string ("D0");
else if (DECL_BASE_DESTRUCTOR_P (dtor))
write_string ("D2");
else
{
gcc_assert (DECL_COMPLETE_DESTRUCTOR_P (dtor)
/* Even though we don't ever emit a definition of
the old-style destructor, we still have to
consider entities (like static variables) nested
inside it. */
|| DECL_MAYBE_IN_CHARGE_DESTRUCTOR_P (dtor));
write_string ("D1");
}
}
3、D2的意义
这个依然是万能的Stack Overflow上给出的答案和例子,
Basically:
- D2 is the "base object destructor". It destroys the object itself, as well as data members and non-virtual base classes.
- D1 is the "complete object destructor". It additionally destroys virtual base classes.
- D0 is the "deleting object destructor". It does everything the complete object destructor does, plus it calls
operator delete
to actually free the memory.