`
evasiu
  • 浏览: 165515 次
  • 性别: Icon_minigender_2
  • 来自: 广州
博客专栏
Fa47b089-e026-399c-b770-017349f619d5
TCP/IP详解卷一>阅读...
浏览量:12264
社区版块
存档分类
最新评论

effective c++ -- 定制new和delete

 
阅读更多
这一章讲述了C++中的new和delete背后的一些机制,以及重写new和delete需要遵守的一些规则,以及什么时候适合重写new和delete以提高效率。

Item 49: 了解new-handler的行为
当operator new无法满足某一内存分配需求时,它会调用一个客户指定的错误处理函数,及new-handler,new-handler可能会为operator new找到足够的内存或者其他怎样以处理内存不足的情况,如果new-handler为空,operator new抛出异常。
通过set_new_handler来设置内存不足时调用的操作,它是声明于<new>的一个标准程序库函数:
namespace std{
  typedef void (*new_handler) ();
  new_handler set_new_handler( new_handler p ) throw();	//承诺不抛出异常
};
//这样使用set_new_handler
void outOfMem(){
  std::err<<"Unable to satisfy request for memory\n";
  std::abort();
}

int main(){
  std::set_new_handler( outOfMem );
  int* pBigDataArray = new int[10000000L];
  ...
}

当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够内存,或者最后抛出异常。因此,一个设计良好的new-handler函数必须做以下事情:
(1)让更多内存可被使用。实现此策略的做法是,程序一开始执行就分配一大块内存,而后当new-handler第一次被调用,将它们释还给程序。
(2)安装另一个new-handler。如果目前这个new-handler无法取得更多可用内存,或许它知道另外哪个new-handler有此能力,因为可以通过set_new_handler安装一个新的new-handler。
(3)卸载new-handler,也就是将null指针传给set_new_handler。一旦没有安装任何new-handler,operator new会在内存分配不成功时抛出异常。
(4)抛出bad_alloc(或派生自bad_alloc)的异常。这样的异常不会被operator new捕捉,因此会被传播到内存索求处。
(5)不返回,通常调用abort或exit。
假如你希望以不同方式处理内存分配失败情况,具体视class而定,也就是提供class之专属new-handler,你可以自己实现出这种行为,只需要令每一个class提供自己的set_new_handler和operator new即可,其中set_new_handler使客户得以指定class专属的new-handler,而operator new则确保在分配class对象内存的过程中以class专属之new-handler替换global new-handler。
class Widget{
  public:
    static std::new_handler set_new_handler( std::new_handler p )throw();
    static void* operator new( std::size_t size ) throw( std::bad_alloc );
  private:
    static std::new_handler currentHandler;
};

std::new_handler Widget::currentHandler = 0;
std::new_handler Widget::set_new_handler( std::new_handler p )throw(){
  std::new_handler oldHandler = currentHandler;
  currentHandler = p;
  return oldHandler;
};

Widget专属的operator new做的事情就是在调用global operator new之前将global new handler安装成Widget专属的new-handler,并在成功分配内存或抛出异常前将原先的global new-handler安装回去。为了达到这一点,可以使用资源管理类:
class NewHandlerHolder{
  public:
    explicit NewHandlerHolder( std::new_handler nh ):handler(nh){}
    ~NewHandlerHolder(){
      std::set_new_handler(handler); }
  private:
    std::new_handler handler;
    //阻止copying
    NewHandlerHolder( const NewHandlerHolder& );
    NewHandlerHolder& operator=( const NewHandlerHolder& );
};
//于是Widget::operator new的实现就相当简单了:
void* Widget::operator new( std::size_t size ) throw ( std::bad_alloc ){
  NewHandlerHolder h(std::set_new_handler(currentHandler));
  return ::operator new(size);
}

事实上,所有希望实现专属set_new_handler的类在这方面的实现基本没有差异,因此我们其实可以把它总结成一个基类:
template<typename T>
class NewHandlerSupport {
  public:
    static std::new_handler set_new_handler( std::new_handler p )throw();
    static void* operator new( std::size_t size ) throw( std::bad_alloc );
    ...
  private:
    static std::new_handler currentHandler;
};
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*( std::size_t size ) throw( std::bad_alloc ){
  NewHandlerHolder h(std::set_new_handler(currentHandler));
  return ::operator new(size);
}

这里的template T完全没有被使用,但是,它确保了每一个derived class获得一个实体互异的currentHandler。

最后,可以使用
Widget* pw2 = new (std::nothrow) Widget;

来获得返回0而不是抛出异常的operator new,但是它只保证operator new不抛出异常,并不保证后续的构造函数不抛出异常,要知道,operator new分配完内存后,还会使用构造函数初始化这片内存。而nothrow只能保证在分配内存时不抛出异常。

Item 50: 了解new和delete的合理替换时机
为什么会想要替换编译器提供的operator new或operator delete呢?下面是三个常见的理由:
(1)用来检测运用上的错误。如果将“new所得内存delete”却不幸失败,会导致内存泄漏。如果在“new所得内存”身上多次delete则会导致不确定行为。如果让operator new持有一串动态分配所得的地址,而operator delete将地址从中移动,便可以很容易地检测出上述错误用法。另外可以在operator new时超额分配内存,以额外空间放置特定的byte pattern,operator delete便得以检查上述签名是否原封不动,若否的话说明在分配区的某个生命时间发生了overrun或underrun。可以通过operator delete将出错的指针载入日志。
(2)为了强化效能。编译器所带的operator new和operator delete主要用于一般目的,它处理的内存请求有时很大,有时很小,它必须处理大数量短命对象的持续分配和归还。它们必须考虑碎片问题。定制版的operator new和operator delete通常在性能上用过缺省版本,它们运行得比较快,需要的内存比较少。
(3)为了收集使用上的统计数据。在定制特定new和delete之前,我们应该收集软件如何使用其动态内存:分配区块的大小分布如何?寿命分布如何?它们倾向于使用FIFO还是LIFO还是随机次序来分配和归还?它们的运用形态是否随时间改变?等等。
(4)为了弥补缺省分配器中的非最佳齐位。
(5)为了将相关对象成簇集中。
(6)为了获得非传统的行为。

Item 51: 编写new和delete时需固守常规
operator new必须遵守的规矩:返回正确的值,内存不足时必得调用new-handling函数,必须有对付零内存的准备,还需避免不慎掩盖正常形式的new。C++规定,即使客户要求0 bytes,operator new也得返回一个合法指针。下面是个non-member operator new的伪码:
void* operator new( std::size_t, size ) throw (std::bad_alloc ){
  using namespace std;
  if( size == 0 )	//处理0 byte申请
    size = 1;
  while( true ){
    //尝试分配size bytes
    void* pMem = malloc(size);
    if( pMem )		//成功分配
      return pMem;
    //分配失败
    //获取当前的new_handler
    new_handler globalHandler = set_new_handler(0);
    set_new_handler( globalHandler );

    if( globalHandler ) (*globalHandler)();
    else throw std::bad_alloc();
  }
}

对于class专属的operator new,还必须注意到它很可能被其派生类继承。而写出定制型内存管理器的一个最常见理由是为针对某特定class的对象分配行为提供最优化,却不是为了该class的任何派生类。也就是说,针对class X而设计的operator new,其行为很典型地只为大小刚好为sizeof(X)对象而设计。然而一旦被继承下去,有可能基类的operator new被调用用以分配派生类对象:
class Base{
  public:
    static void* operator new(std::size_t size) throw( std::bad_alloc );
    ...
};
class Derieved : public Base{
  ...
};

Derieved* p = new Derieved;	//这里调用的是Base::operator new
//因此,假如operator new是被专门设计用以返回Base对象大小的内存
//有必要为此情况做出一点处理:
void* Base::operator new( std::size_t size ) throw ( std::bad_alloc ){
  if( size != sizeof(Base) )
    //注意,sizeof(Base)一定不为0,所以size为0的情况将交给::operator new
    return ::operator new(size);
  ...
}

如果你打算控制class专属之arrays内存分配行为,那么便需要实现operator new的array兄弟版:operator new[],这时你唯一需要做的一件事就是分配一块未加工内存。
至于operator delete,唯一需要记住的是:C++保证“删除null指针永远安全”。另外,如果你的class专属operator new将大小有误的分配行为转交::operator new执行,那么也必须将大小有误的删除行为转交::operator delete:
class Base{
  public:
    static void* operator new( std::size_t size ) throw( std::bad_alloc );
    static void operator delete( void* rawMemory, std::size_t size ) throw();
    ...
};

void Base::operator delete( void* rawMemory, std::size_t size ) throw(){
  if( rawMemory == 0 )
    return;
  if( size != sizeof(Base){
      ::operator delete(rawMemory);
      return;
  }
  return;
}


Item 52: 写了placement new也要与placement delete
所谓的placement new,是相对于正常的operator new而言的,或者从广义上说,应该是“被重载的operator new”(我个人是这么感觉的)。
正常的operator new及其相应的operator delete版本如下:
void* operator new( std::size_t ) throw ( std::bad_alloc );
void operator delete(  void* rawMemory ) throw ();
//class作用域中的delete,包含一个大小
void operator delete( void* rawMemory, std::size_t size )throw();

new表达式:
Widget* pw = new Widget;

实际调用了两个函数,一个是用以分配内存的operator new,一个是Widget的default构造函数。
假设其中第一个函数调用成功,即内存已经被分配,第二个函数却抛出异常。此时,pw尚未被赋值,被内存已经被分配,但是我们没有得到指向该内存的指针,因此我们没有能力释放被分配的内存。因此,释放内存的责任就落在了C++运行期系统上。运行期系统将会找到与刚才调用的operator new相配对的operator delete来执行这一任务。
假设我们为Widget写了一个专属的operator new,要求接受一个ostream用以记录分配信息:
class Widget{
  public:
    static void* operator new( std::size_t size, std::ostream& log )
      throw( std::bad_alloc );
    ...
};

“如果operator new接受的参数除了一定会有的那个size_t之外还有其他,这便是个所谓的placement new”。
最早的placement new版本是一个“接受一个指针指向对象被构造之处”的operator new,其长相如下:
void* operator new( std::size_t, void* pMemory ) throw();

它的用途之一是负责在vector的未使用空间上创建对象。
如果在调用
Widget* pw = new (std::err) Widget;

时Widget构造函数抛出异常,C++运行期系统将自动调用与operator new相对应的版本来释放内存。所谓的“相对应的版本”,是指“参数个数和类型都与operator new相同”的某个operator delete,因此,当你定义了一个placement new时,你必须定义一个对应的placement delete,否则系统找不到对应的delete,无法执行内存回收工作,那么内存就泄漏了。
另外,operator new函数的名字匹配规则同一般的成员函数一样,如果你只在基类里声明了一个placement new,关于基类的new调用将无法使用正常版本;然后如果你又在继承自该基类的派生类里重新声明正常形式的new,基类的placement new也将不能默认作用在该派生类上。总之就是,operator new的名字匹配规则跟一般的成员函数是一样的。
placement delete只有在“伴随placement new调用而触发的构造函数”出现异常时才会被调用。

书中最后一章提醒了编译器warning的重要性,介绍了std::tr1及boost的一些基本东西。到此,这本书就总结完了。然后寒假也结束了~~~希望接下来的一学年好运吧。
分享到:
评论

相关推荐

    Effective C++ 中文版

    内容简介: 有人说C++程序员可以分成两类,读过Effective C++的和没读过的。世界顶级C++大师Scott Meyers成名之作的第三版的确当得起这样的评价...8.定制new和delete 9.杂项讨论 A 本书之外 B 新旧版条款对映 索引

    More Effective C++

    和其前一本兄弟书籍 Effective C++一样,More Effective C++对每一位以C++为开发工具的程序员而言,都必备读物。  继 Effective C++ 之後,Scott Meyers 於 1996 推出这本「续集」。条款变得比较少,页数倒是多了...

    Effective C++

    Effective C++(编程的50个细节)着重讲解了编写C++程序应该注意的50个细节问题,书中的每一条准则描述了一个编写出更好的C++的方式,每一个条款的背后都有具体范例支持,书中讲的都是C++的编程技巧和注意事项,很多都...

    Effective C++(第三版)

    8. 定制new和delete customizing new and delete 条款49:了解new-handler的行为 understand the behavior of the new-handler. 条款50:了解new和delete的合理替换时机 understand when it makes sense to replace ...

    c++ Effective STL(中文+英文)

    条款7: 当使用new得指针的容器时,切记在容器销毁前delete那些指针 条款8: 千万不要把auto_ptr放入容器中 条款9: 小心选择删除选项 条款10: 当心allocator的协定和约束 条款11: 了解自定义allocator的正统使用法...

    Effective.C++.中文第二版.50条款doc文档.chm

    Effective.C++.中文第二版,大小 1 Mb,chm 格式,作者:Scott Meyers,翻译:Lostmouse。 内容预览: 第一章 从C转向C++ 条款1:尽量用const和inline而不用#define 条款2:尽量用而不用 条款3:尽量用new和delete而...

    EffectiveC++ and more Effective C++

     ·条款八:理解各种不同含义的new和delete  ·条款九:使用析构函数防止资源泄漏  ·条款十:在构造函数中防止资源泄漏  ·条款十一:禁止异常信息(exceptions)传递到析构函数外  ·条款十二:理解...

    Effective c++

    提高编程效率的50条建议 条款1:尽量用const和inline而不用#define 条款2:尽量用而不用 条款3:尽量用new和delete而不用malloc和free 内存管理的建议 设计与说明的建议 继承与面向对象的设计 杂项

    完结7章C++大厂面试真题宝典 精选100道

    C和C++动态管理内存的方法不一样,C是使用malloc/free函数,而C++除此之外还使用new/delete关键字。 C++的类是C里没有的,但是C中的struct是可以在C++中正常使用的,并且C++对struct进行了进一步的扩展,使得struct...

    Effective STL(中文)

    &lt;br&gt;《Effective STL》目录: &lt;br&gt;前言 致谢 导读 容器 条款1:仔细选择你的容器 条款2:小心对“容器无关代码”的幻想 条款3:使容器里对象的拷贝操作轻量而正确 条款4:用empty来代替...

    effective stl stl 技巧

    条款7:当使用new得指针的容器时,记得在销毁容器前delete那些指针 条款8:永不建立auto_ptr的容器 条款9:在删除选项中仔细选择 条款10:注意分配器的协定和约束 条款11:理解自定义分配器的正确用法 条款12...

    effective stl 中文 pdf

    条款7: 当使用new得指针的容器时,切记在容器销毁前delete那些指针 条款8: 千万不要把auto_ptr放入容器中 条款9: 小心选择删除选项 条款10: 当心allocator的协定和约束 条款11: 了解自定义allocator的正统使用法...

    代码语法错误分析工具pclint8.0

    new),其中最后一个选项是operator new,那么在operator和new中间只能有一个空 格。 选项还可以放在宏定义中,例如: #define DIVZERO(x) /*lint -save -e54 */ ((x) /o) /*lint -restore */ LINT的选项很多...

    WPViewPDF v3.11 VCL/.NET/ActiveX (x32/x64) + Crack

    You can extract PDF pages or delete pages. It is also possible to add vector and text graphic to PDF files (stamping). Unlike many competing products WPViewPDF displays even large PDF files ...

    Effictive STL CHM中文版

    条款7: 当使用new得指针的容器时,切记在容器销毁前delete那些指针 条款8: 千万不要把auto_ptr放入容器中 条款9: 小心选择删除选项 条款10: 当心allocator的协定和约束 条款11: 了解自定义allocator的正统使用法...

Global site tag (gtag.js) - Google Analytics