首先看下面一段代码,找出代码中存在的问题:
class B
{
public:
virtual ~B();
void operator delete(void*, size_t) throw();
void operator delete[](void*, size_t) throw();
void f(void*, size_t) throw();
};
class D : public B
{
public:
void operator delete (void*) throw();
void operator delete[](void*) throw();
};
下面我来说一下以上代码的问题:
1. 有operator delete
但没有相对应的operator new
,这是很容易引起内存问题的。
2. operator delete
没有声明成静态的。当然这里虽然没有声明成静态的,编译器也会默认把operator delete
和operator new
当作静态的函数,但为了可读性,还是要把operator delete
和 operator new
显式的声明为静态的。
3. 注意到基类里声明operator delete
的时候多了一个size_t
参数,而派生类里是没有的,这样会有什么问题吗?答案是没有任何影响,写与不写都可以,看自己的喜好。
另外,看一下下面的代码:
B* pb1 = new D;
delete pb1;
问题是以上代码可以正确调用到D的operator delete
吗,答案是可以的。这个时候你可能会问了,既然operator delete
是静态的,那为什么用pb1可以调用到类D的operator delete
呢?这个就要感谢编译器为我们做的事情了。首先类B的析构函数是虚的,所以再调用pb1的析构函数时,会调用到类D的析构函数中去,而这个时候,c++会在析构函数中设置一个标志,如果说这个对象是静态分配的,那这个标志为false,但是,如果这个对象是动态分配的,那么这个标志就会为true。而当这个标志为true的时候,c++就会调用相应的delete去释放掉这块内存区域。这也就解释了为什么可以调到类D中的operator delete
了。当然,这里类B的析构函数如果是非虚的,那么在调用pb1的析构函数时,就不会调用到类D的了,这个时候不但类D没有正确被析构,相应的operator delete
调用也是错误的。
下面再来看一段代码:
class ShareMemery
{
public:
static void* Allocate(size_t s)
{
return OsSpecificShareMemAllocation(s);
}
static void Deallocate(void *p)
{
OsSpecificShareMemDeallocation(p);
}
};
class Y
{
public:
static void* operator new(size_t s,
SharedMemery& m) throw(bad_alloc)
{
return m.Allocate(s);
}
}
SharedMemery shared;
...
new (shared) Y; // if Y::Y() throws, memory is leaked
我相信读者应该很快能看出上面代码的问题,问题就是缺少了相对应的operator delete
。下面我们完善一下以上的类:
class Y
{
public:
static void* operator new(size_t s,
SharedMemory& m) throw(bad_alloc)
{
return m.Allocate(s);
}
static void operator delete(void* p, SharedMemory& m) throw()
{
return m.Deallocate(p);
}
static void operator delete(void* p) throw()
{
return SharedMemory::Deallocate(p);
}
}
首先这里要注意的是提供了两个operator delete
,为什么要这样呢?
第一,一定要有一个和operator new
相对应的operator delete
,这里的对应只的是,除了第一个参数外,剩余的参数可以一一对应,之所以要这样配对出现,是为了避免出现内存泄露。假设在调用我们自己的operator new
时,对象的初始化出现了异常,这个时候c++会去调用配对的operator delete
去释放内存,加入编译器找不到配对的operator delete
,那么这个内存就泄露了。另外,为什么提供第二种形式的operator delete
呢,这是因为我们在调用delete Y
的时候,此时并不会调用配对的那个,因为这里没有传参,也是不可以传参的,所以要提供第二种形式的operator delete
。
综上所述,其实最重要的就一点,那就是自定义的operator new
和operator delete
一定要配对出现,要不然的话,就会出现内存泄露,甚至程序崩溃等问题。