【Effective C++】读书笔记 条款49~51

定制new和delete

条款49:了解new-handler的行为

1. 了解new-handler

当operator new无法满足某一内存分配需求时,它会先调用一个客户指定的错误处理函数,(默认情况下这个错误处理函数是空,operator new会直接抛出异常)。为了指定这个”用以处理内存不足”的函数,客户必须调用set_new_handler,那是声明于的一个标准库函数,他将执行一个new-handler类型的函数指针。如下:

namespace std{
    typedef void (*new_handler)();
    new_handler set_new_handler(new_handler p) throw();
}

new_handler是一个typedef,它的类型是参数和返回值都为void的函数的函数指针,set_new_\handler的new_handler型参数用于指定当无法分配足够内存时调用的函数,set_new_handler返回的函数指针指向在此之前用处理内存不足当马上就要被替换的函数。

new_handler的具体用法如下:

//下面是无法分配足够内存时,应该被调用的函数
void outOfMem(){
    std::cerr<<"Unable to satisfy  request for mempry/n"<<endl;
    std::abort();
}
int main(){
    std::set_new_handler(outOfMem);
    int* pBigDataArray=new int[1000000000];
    ...
}

如果operator new无法分配1000000000个int变量所需空间,它不会立即抛出异常,而是调用outOfMem,因为outOfMem已经被设置为默认的new-handler。并且在以后发生异常后,同样也会调用这个outofMem,因为原本的内存处理函数已经不在了。

一个new-handler的主要作用

  1. 让更多内存可被使用,然后下一次调用new的时候有可能就会调用成功。
  2. 安装另一个new-handler,如果当前new-handler无法获取更多内存但它知道另一个new-handler有此能力,它可以调用set_new_handler将那个new_handler设为默认new_handler使得下一次operator new调用的new-handler是最新的那个。
  3. 卸除new-handler.将NULL指针传给set_new_handler,如果没有安装任何new-handler,当operator new分配内存不成功时抛出异常。
  4. 抛出bad_alloc(或派生自bad_alloc的)异常.这样的异常不会被operator new捕捉,因而会被传递到内存索求处.
  5. 不返回。调用abort或exit。

2. 设计class专属的new-handlers

当我们调用set_new_handler时候,设置的是全局内存错误处理函数。C++自身也不支持class专属的new-handlers。但是我们可以实现如下:

需要每一个class提供自己的set_new_handler和operator new。其中set_new_handler用于设定class专属的new-handler,operator new确保在分配class内存时以class专属的new-handler替换global new-handler,并在class专属的new-handler完成职责后将默认new-handler替换为global new-handler。

class的设计如下:

class Widget{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static std::operator new(std::size_t size) throw(std::bad_alloc);
private:
    static std::new_handler currentHandler;
};

std::new_handler Widget::set_new_handler(std::new_handler p) throw(){
    std::new_handler oldHandler=currentHandler;  //保存旧的new-handler
    currentHandler=p;
    return oldHandler;
}

std::new_handler Widget::currentHandler=0;

其中Widget的operator new做以下事情:

  1. 调用set_new_handler,将Widget专属的new-handler安装为global new-handler.
  2. 调用global operator new用来实际分配内存,如果分配失败,global operator new会调用Widget的new-handler,因为那个函数才刚被安装为new-handler.如果global operator new最终无法分配足够内存,会抛出一个bad_alloc异常,在此情况下Widget的operator new必须恢复原本的global new-handler,然后再传播该异常,这就需要将采用条款13的思想——以资源管理对象.
  3. 最后,如果global operator new成功分配一个Widget对象所用内存,Widget的operator new应该返回一个指针,指向分配所得。并且也要恢复原来的new-handler。

结合条款13来实现相应的资源管理类来实现new-handler的自动管理。

class NewHandlerHolder{
public:
    explict NewHandlerHolder(std::new_handler nh):handler(nh){}
    ~NewHandler(){std::set_new_handler(handler);}
private:
    std::new_handler handler;  //用于保存global new-handler
    NewHandlerHolder(const NewHandlerHolder&);
    NewHandlerHolder& operator=(const NewHandlerHolder&);
}
void* Widget::operator new(std::size_t size) throw(std::bad_alloc){
    NewHandlerHolder h(std::set_new_handler(currentHandler));//安装new-handler并使h保存global new-handler
    return ::operator new(size);  //h析构时恢复global new-handler
}

Widget的客户类似这样使用其new-handler:

void ourOfMem();
Widget::set_new_handler(outOfMem);
Widget* pw1=new Widget;  //如果内存分配失败,调用的是outOfMem
std::string *ps=new string;   //如果内存分配失败,调用的是global new-handler
Widget::set_new_handler(0);
Widget* pw2=new Widget;  //如果内存分配失败,直接抛出异常

3. 将class专属的new-handler机制泛化为模板

实现这一个方案的代码并不因为class的不同而不同,因此为了实现复用,我们可以将这个方案建立成一个base class,然后derived class继承此base class从而完成能够“能够设定class专属的new-handler的能力”。将这个base class转换为template,让每个derived class都获得实体互异的class data复件。

template<typename T>
class NewHandlerSupport{
public:
    static std::new_handler set_new_handler(std::new_handler p) throw();
    static std::operator new(std::size_t size) throw(std::bad_alloc);
    ...
private:
    static std::new_handler currentHandler;
};

//定义cureentHandler
template<typename T>
std::new_handler NewHandlerSupport<T>::currentHandler=0;

template<typename T>
std::new_handler NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw()
{
    std::new_handler oldHandler=currentHandler;
    currentHandler=p;
    return oldHandler;
}

template<typename T>
void* NewHandlerSupport<T>::operator new(std::size_t size) throw(std::bad_alloc)
{
    NewHandlerHolder h(std::set_new_handler(currentHandler); 
    return ::operator new(size); 
}

有了这个class template,就可以为Widget和其他类添加set_new_handler支持能力了——只要令Widget继承自NewHandlerSupport就好:

class Widget:public NewHandlerSupport<Widget>{
    ...
}

类模板NewHandlerSupport看起来相当奇怪,因为参数T从未被使用,实际上参数T只是用来区分不同的derived class,使继承自NewHandlerSupport的每一个class都有自己独立的static成员变量currentHandler。至于Widget继承自一个以Widget为参数的类模板具现化的类,这是被C++允许的,而且拥有自己的名称——”怪异的循环模板模式”(curiously recurring template pattern;CRTP).

NewHandlerSupport类模板的构建使得”为任何class添加一个它们专属的new-handler”成为易事,但是却又可能导致多重继承。

4. nothrow new所保证的无异常抛出是十分有限的

class Widget{...}
Widget* pw1=new Widget;

由于以上new Widget表达式将会发生如下操作,调用nothrow版的operator new,调用Widget的默认构造函数。

因而尽管nothrow版的operator new保证不抛出异常,但这并不能阻止Widget的默认构造函数抛出异常。

所以nothrow new只能保证operator new不抛出异常,但是不能保证“new(std::nothrow) Widget”这样的表达式绝不抛出异常。所以其作用十分有限。

4. 总结

  1. set_new_handler允许客户指定一个函数,在内存分配无法获得满足时候被调用。
  2. nothrow new是一个十分有限的工具,它只适用于内存分配,后继的构造函数调用还是可能抛出异常。

条款50:了解new和delete的合理替换时机

本条款主要解释了在某些时候,你可能想要自己定制operator new和operator delete行为。

对于operator new 和 operator delete,可以选择是否重载。

以下是重载operator new 和 operator delete的一些理由:

  1. 用来检测运用上的错误。如可以检测内存泄漏,或者检测分配的区块有没有发生overrun(写入数据到分配区块的尾端之后),或者underrun(写入数据到分配区块的头部之前)。
  2. 为了强化效能。编译器所带的operator new和operator delete要处理各种各样的请求。所以他的性能是对任何工作都适度的好,但是不会对任何工作有特定表现。对于自己定制的operator new和operator delete,往往能够获得效率的重大提升。
  3. 为了收集使用上的统计数据。

条款51:编写new和delete时需固守常规

本条款中说明了在定制operator new和operator delete的时候需要遵守的一些规定。

自己定制operator new的时候需要注意的事项:

  1. operator new的操作如果能够成功,就返回指针指向申请的内存,否则就将抛出bad_alloc函数。
  2. operator new在尝试分配内存失败后,会循环调用 new-handing函数,理论上new-handing函数将执行某些行为来处理目前这个状况,只有当new-handing函数的指针是null,operator new才会抛出异常。
  3. 要能够处理客户需求是零内存。
  4. 如果将其定制为某个class的member 函数,一般还需要检查申请的内存大小和本类的内存大小是否相同,否则就转发给全局operator new。

如果想要定制operator new[],要做的唯一一件事情就是分配一块未加工内存。

对于定制的operator delete

  1. 要记住C++保证删除null指针永远安全。
  2. 果将其定制为某个class的member 函数,一般还需要检查删除的内存大小和本类的内存大小是否相同,否则就转发给全局operator delete。

总之:

  1. operator new应该内含一个无穷循环,并在其中尝试分配内存,如果它无法满足内存需求,就应该调用new-handler。它也应该有能力处理 0 bytes申请。Class专属版本的operator new则还应该处理“比正确大小更大的错误申请”。
  2. operator delete应该在收到null指针时不做任何事。Class专属版本要处理“比正确大小更大的错误申请”。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值