Home > 程序/算法 > 没有placement delete

没有placement delete

May 28th, 2009

什么是placement delete?先得说说什么是placement new.

我们都知道在c++里我们可以重载operator new,也就是自定义new的行为,这样你就能取代标准new函数成为默认的内存提供者,大概是这样写的

void* operator new( size_t size ){//…}

你可以在重载的new函数里做任何事情,比如实现一个内存池:假如你的程序会以很高的频率申请小块内存,而且直到程序退出也不会释放他们,那么就可以很简单地,在启动的时候malloc一大块内存,每次new的时候分出来一小块,最后再整个内存块释放掉,能极大提高new的效率,并且减少系统内存碎片的产生。在另外一些情况下,你需要统计自己的程序当前用掉了多少内存,也可以在重载的new里进行(当然,你也需要同时重载delete以便在内存被释放的时候及时从统计数据中扣除)

看起来很美,但是这种重载有较大的风险,因为为了方便你通常会把这个重载函数的声明放在某个头文件里,这时一些不希望使用你的重载版operator new的cpp包含了你的这个头文件,就被迫也使用了你的new,怎么办呢?这时候你就需要用到placement new,简单说就是为operator new添加一个参数,象这样:

void* operator new( size_t size, MyAllocator& allocator )

{

return allocator.allocateChunk( size );

}

这样,你只有在显式附带了指定类型的参数时,调用的才是你的new,比如 int* p = new(allocator) int[100]; 如果你只是写int* p = new int[100]; 那么还是调用默认的new(或者已经被什么地方重载的另一个new)。为了方便起见,你还可以定义#define MY_NEW new(g_allocator),这样你要用自己的new时就用MY_NEW替换new,而不会影响那些想使用默认new的地方。

写了这么多,即使你不知道,也应该发现了:不管是直接重载operator new还是定义自己的placement new,你只能控制内存的申请,之后的对象构造不是由你负责的。那么在另外一边,一定会有一个只用管内存释放,而不用操心对象析构的东西吧?对于重载的operator new,情况的确是这样,你可以重载operator delete,就像:

void operator delete( void* p )

{}

需要统计内存使用的同学注意了,这里没有给出待释放内存的size,你要自己想办法,不过这是题外话。真正的问题是,也就是标题上说的,你不能写一个placement delete!也就是说你这样写:

void operator delete( void* p, MyAllocator& allocator )

{

allocator.deallocateChunk( p );

}

不能得到你希望的结果,你调用了delete( allocator ) p;你会看到析构函数被调用了,但是你的”placement delete”并没有被调用!而且,虽然这个重载delete函数屁用没有,但你要是不写的话,编译器还会抱怨warning C4291: no matching operator delete found; memory will not be freed if initialization throws an exception。因为你提供了placement new,所以你也必须提供这个伪placement delete。这个问题好象困扰了不少人,我觉得作为一个严谨的语言,c++不应该不满足这个看起来明显很合理的需求,STFW了好一阵,结果发现c++他爹都说了,没有placement delete! (原文在这里),他原话是这么说的(虽然我还没有完全理解):The reason that there is no built-in “placement delete” to match placement new is that there is no general way of assuring that it would be used correctly.他老人家还建议说(也是我现在的解决方法),可以这样来模拟placement delete的功能:

template<class T> void MY_DELETE( const T* p )

{

p->~T();

g_allocator.deallocateChunk( p );

}

看上去也挺美,而且还用上了模板显得很高级。但是这里有一个不太明显的问题,如果p的类型是p真正指向的对象的类型的非第一个父类(你明白我在说什么就怪了),举例说:

class A

{//…};

class B

{//…};

class C : public A, public B

{//…};

现在我这么干:

C* pC = MY_NEW C;

B* pB = pC;

MY_DELETE( pB );

会发生什么呢?先说明一下,对于绝大多数编译器(如果不是全部的话),上面例子中的pB和pC都是不相等的。如果你不想得到免费的午餐,看到这里你可以自己打开vc,写一些测试代码,然后调试看看,然后再继续……

欢迎回来。你应该已经发现,如果你的A,B类的析构是virtual的话,基于某些c++“魔术”,这么写:delete p;能够调用正确的析构(C以及A,B的析构),但是对于可怜的MY_DELETE(或者说你的allocator)没有任何(简单的)办法知道p所真正指向的对象的类型,于是不能得到对象C的真正首地址,直接对p进行deallocate显然是不正确的。

所以,使用这种MY_DELETE之前我们必须约定,最好不要多重继承,如果非要多重继承的话,删除时请给我对象的第一个父类的指针:)

非要钻牛角尖的话,如果你知道你的编译器会把类型信息藏在虚表的什么地方,你也可以自己去挖掘出正确的对象首地址,但是记住要查一下c++标准有没有规定类型信息必须放在那里,否则当你换了一个编译器,你的程序可能就不能正常工作了。

Categories: 程序/算法 Tags:
Comments are closed.