menu

hjk41的日志

Avatar

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可以透明的采用内存池管理对象

评论已关闭