C++编程规范中都不支持显示的调用析构函数,部分文章中甚至说明析构函数是不能显示调用的,然而执行如下类似的操作编译器并不会报错,而且会调用成功。
pa->~A();
我们先来看看这样的操作会有哪些副作用,然后再讨论存在的意义;
1. 如果在析构函数中显示地释放了堆上的内存,显示调用析构函数会存在重复释放内存错误,例子如下:
class A
{
char *a;
public:
A(){a =(char *)malloc(4);}
~A()
{
free(a);
printf(“Adeconstructor\n”);
}
};
int main()
{
A a;
a.~A();
return0;
}
2. 显示的调用析构函数相当于执行了析构函数内容(包括子类析构函数),但是并没有释放内存,也没有摧毁对象;上述的例子可以理解为a对象占用了char *大小的栈内存,占用了4个字节的堆内存,堆内存通过构造函数申请,通过析构函数释放;显示的调用析构函数执行了析构函数,释放掉了堆上的内存,但是并不会释放栈上内存。
3. 使用new操作符的情况有写不同,new操作符可以考虑为两个阶段。
a. 在堆上申请对象本身成员所占内存;
b. 执行构造函数。
考虑如下例子:
class A
{
char *a;
public:
A(){a =(char *)malloc(4);}
~A()
{
free(a);
printf(“Adeconstructor\n”);
}
};
int main()
{
A *a =new A();
a->~A();
return0;
}
显然此时编译器不会隐式调用析构函数,所以不会出现重复释放的问题,但是由于a->~A()只会执行析构函数,a指针本身所占的内存不会被释放掉,所以会造成内存泄漏。
由以上的分析可知,显示调用析构函数不但不会带来任何好处,还会造成很多奇怪、难以分析的问题,这也是不推荐使用的原因,为什么C++标准不禁止呢?来看一下关于placementnew的用法。
placement new是operator new的一个重载版本;所谓placement new就是在用户指定的内存位置上构建新的对象,这个构建过程不需要额外分配内存,只需要调用对象的构造函数即可。
例子如下:
struct A {…};
struct B {…};
A* pa = new A;
pa->~A();
B* pb = new (pa) B;//placement new
以上例子必须要求sizeof(A)>=sizeof(B)。B* pb = new (pa) B并不会申请内存,只是在原来A对象的位置上生成B的对象。
更普遍的使用方法如下:
class A{
…
public:
show();
};
1)分配内存
char* buff = new char[ sizeof(A) *N+sizeof(int)) ];
memset( buff, 0, sizeof(A)*N +sizeof(int));
2)构建对象
A* pa = new (buff)A;
3)使用对象
pa->show();
4)析构对象,显式的调用类的析构函数。
pa->~A();
5)销毁内存
delete [] buff;
对于buff这块内存可以反复使用,只要重复2)、3)、4)步骤即可。在C++标准中,对于placementoperator new []有如下的说明:placementoperator new[] needs implementation-defined amount of additional storage tosave a size of array. 所以我们必须申请比原始对象大小多出sizeof(int)个字节来存放对象的个数,或者说数组的大小。
对于效率和容错要求比较高的程序来说placement new是很有用的:
1) placement new能够有效的解决内存碎片的问题;
2) malloc、new等申请内存有失败的可能,对有些程序来说是不允许的,而placementnew可以做到;
3) placement new不需要执行申请内存的过程,速度更快;
C++标准STL就有使用placement new的情况,不过不是在特殊情况下比如自己编写高效率库、需要自己管理内存池时不推荐使用placement new。