Effective C++ 笔记8: 重载 new 和 delete 时要遵从的惯例
有时候我们会重载操作符 new 和 delete (为什么?现在我还没想明白为什么要这么干),这时就要遵从一些惯例。
new 的惯例
操作符 new 必须接收返回一个 void * 指针,如果申请内存失败则要调用错误处理函数(前一次已经讲了)。前一个要求容易满足,而在做错误处理时就要注意一点了,正如前一节所说的,new 认为错误处理函数能释放一些内存,因此它碰到内存不足就会调用错误处理函数,然后再重新申请一次内存,如果还不行,就再调用一次错误处理函数,直到申请成功或是错误处理将程序中断。
第三点就是 new 一个0字节的空间是没错的,这点也应该考虑。
下面是一个操作符 new 的伪码:
void * operator new(size_t size) // your operator new might
{ // take additional params
if (size == 0) { // handle 0-byte requests
size = 1; // by treating them as
} // 1-byte requests
while (1) {
attempt to allocate size bytes;
if (the allocation was successful)
return (a pointer to the memory);
// allocation was unsuccessful; find out what the
// current error-handling function is (see Item 7)
new_handler globalHandler = set_new_handler(0);
set_new_handler(globalHandler);
if (globalHandler) (*globalHandler)();
else throw std::bad_alloc();
}
}
这里可以看到,new 里面有一个 while(1) 循环,它会一直循环,直到成功返回一个指针,或是由错误处理函数跳出。我们可以看到最后四行代码比较奇怪,先执行 set_new_handler(0),然后立即又执行 set_new_handler(globalHandler),主要是为了取得当前的错误处理函数的指针,因为C++里并没有提供别的方法来取得这个指针。取得这个指针主要是为了判断它是不是为空指针,如果指针为空就立即抛出一个异常。
另一个要注意的是,操作符 new 会被派生类所继承,所以要注意,因为派生类一般比基要大,所以调用基类的 new 可能会出问题。
class Base {
public:
static void * operator new(size_t size);
...
};
class Derived: public Base // Derived doesn't declare
{ ... }; // operator new
Derived *p = new Derived; // calls Base::operator new!
这里的对*p调用的就是基类的 new ,这样 p 所指向的内存空间大小就不对。所以一般我们会这么写:
void * Base::operator new(size_t size)
{
if (size != sizeof(Base)) // if size is "wrong,"
return ::operator new(size); // have standard operator
// new handle the request
... // otherwise handle
// the request here
}
这样在调用 Derived *p=new Derived; 时,new 收到的参数 size 就会是类 Derived 的大小,这样实际上最后被调用的是 ::operator new(size) ,就不会出错了。
delete 的惯例
C++标准规定,对一个空指针进行 delete 操作是没错的,所以我们重载 delete 时也应该保证这种操作的正确性。
下面是一段 delete 的伪码:
void operator delete(void *rawMemory)
{
if (rawMemory == 0) return; // do nothing if the null
// pointer is being deleted
deallocate the memory pointed to by rawMemory;
return;
}
跟 new 一样,delete 也会被派生类继承,所以也要进行相应判断:
class Base { // same as before, but now
public: // op. delete is declared
static void * operator new(size_t size);
static void operator delete(void *rawMemory, size_t size);
...
};
void Base::operator delete(void *rawMemory, size_t size)
{
if (rawMemory == 0) return; // check for null pointer
if (size != sizeof(Base)) { // if size is "wrong,"
::operator delete(rawMemory); // have standard operator
return; // delete handle the request
}
deallocate the memory pointed to by rawMemory;
return;
}
我碰到过一次需要重载new和delete的情况,就是我们采用一种内存拷贝的方式来建立一个类,使类成员的内存空间连续,这样可以直接把某一部分扔给网络层,这是相当特殊的情况,通常的操作在构造函数和析构函数里面做就可以了。
其实还可以通过重载new和delete实现singleton模式,而且这样的方式可以对外面完全透明。而通常实现singleton模式的方法是采用getInstance()方法。
重载new和delete可以透明的采用内存池管理对象