zoukankan      html  css  js  c++  java
  • 【C++】定制new和delete

    new和delete是C++中进行内存分配和释放的基本接口,程序员对内存的管理就是依靠这两个接口完成.

    既然C++的编译器已经提供了new和delete接口,那我们为什么重新定制new和delete.

    这就引出第一个问题,什么情况下需要重新定制new和delete?(肯定是编译器提供的new和delete满足需求的时候)

    1.用来检测运用上的错误

      编程的过程中,可能出现导致数据"overruns"(写入点在分配区块尾端之后)或"underruns"(写入点在分配区块起点之前).如果我们自行定义一个operator news,便可超额分配内存,以额外空间(位于客户所得区块之前或后)放置特定的byte patterns.operator deteles便可以检查上述签名是否没有变化,若否就表示在分配区某个生命时间点发生了overrun和underrun,此时operator delete便可以记录这个事实.

    2.为了强化效能

      各种各样的应用对内存管理器的要求,也各不相同,编译器为了满足各种各样的分配形态,其提供的operator news和operator deletes采取中庸之道,它们的工作对每个人都是适度地好,但不对特定的任何需要有最佳表现.如果对程序的动态内存运用型态有深刻的了解,定制operator new和operaotr delete,其性能可比缺省的好.

    3.为了收集使用时的统计数据

      在这制自己的operator new和operator delete之前,我们必须了解程序是如何使用动态内存的.分配区块的大小分布如何?存命分布如何?它们倾向于以FIFO次序或LIFO次序或随机次序来分配和归还等,为了收集这些信息,我们自定义operator new和operator delete.

    4.为了增加分配和归还的速度

      泛用型分配器往往比定制型分配器慢,特别是当定制型分配器专门针对某特定类型对象而设计时.当然,在获得"operator new"和"operator delete"有加快程序速度的价值这个结论之前,我们必须分析程序,确认程序瓶颈的确发生在那些内存函数身上.

    5.为了降低缺省内存管理器带来的空间额外开销

      泛用型分配器往往使用更多的内存,尤其是分配小型对象时,会造成资源浪费,因为它们常常在每一个分配区块身上招引某些额外开销.针对小型对象而开发的分配器可以消除这样的额外开销.

    6.为了弥补缺省分配中的非最佳齐位

      在x86体系结构上doubles的访问最是快速,如果它们都是8-byte齐位.但编译器自带的operator news并不保证对动态分配而得的doubles采取8-bytes齐位.在这种情况下,将缺省的operator new替换为一个8-byte齐位保证版,可导致程序效率大幅提升.

    7.为了将相关对象成簇集中

      如果我们知道特定的某个数据结构往往被一起使用,而我们又希望在处理这些数据据时将"内存页错误"的频率降至最低,那么我们可以为些数据结构创建另一个heap,让它们集中在尽可能少的内存页上.

    8.为了获得非传统的行为

      有时候我们需要operator news和deletes做编译器附带版没有做的某些事情.例如分配和归还共享内存内的区块,但唯一能够管理该内存的只有C API函数,因而写一个定制的new和delete,在C API的基础上添加一层C++的外套.

    现在我们知道了定制new和delete的时机,我们就要开始动手定制new和delete.

    在动手定制new和delete之前,我们需要了解编译器提供new和delete的机制,相当于在打造自己的轮子之前,先看看现在的轮子,参考现有的实现.

    具体的实现,这里不作详细介绍,只是介绍一下里面new-handler处理机制

      编译器提供的new,在内存不足的情况下,自己的一套处理机制.当operator new抛出异常以反映一个未满足的内存需求之前,它会调用一个客户指定的错误处理函数,new-handler.

    当我们自己定制new和delete时,也得提供相应的处理机制.我们可以利用set_new_handler来指定自己的错误处理函数.具体使用方法如下:

     1 namespace std {
     2     typedef void (*new_handler)();
     3     new_handler set_new_handler(new_handler p) throw();
     4 }
     5 
     6 void outOfMem()
     7 {
     8     std::cerr<<"Unable to satisfy request for memory\n";
     9     std::abort();
    10 }
    11 int main()
    12 {
    13     std::set_new_handler(outOfMem);
    14     int* pBigDataArray = new int[1000000000L];
    15     ...
    16 }

    关键是我们该如何实现错误处理函数,让其更满足我们的需求.一般一个设计良好的new-handler函数必须做以下事情

    1.让更多内存可被使用.

    2.安装另一个new-handler.

    3.卸除new-handler.

    4.抛出bad_alloc(或派生自bad_alloc)的异常.

    5.不返回.通常调用abort或exit.

    虽然我们可以通过set_new_handler指定错误处理函数,以不同的方式处理内存分配失败的情况.

    但每次调用set_new_handler都是设置全局的错误处理函数,当我们需针对不同类的对象设定不同的错误处理函数时,需要调用set_new_handler多次.

    此外,为了保证一般情况下,默认的错误处理函数被调用,我们还必须在设置不同的错误处理函数之前,保存默认的错误处理函数,以便重新设定回原始状态.

    如何才能保证以不同的方式处理内存分配失败的情况,其处理内存方式依据对象的类型而定,即class专属的new-handlers.

    下面介绍一下,如何实现class专属的new-handler.

    实现思路:

      令每个class提供自己的set_new_handler和operator new,其中set_new_handler使用户可以指定class专属的new-handler,operator new则确保在分配class对象内存的过程中以class专属的new-handler替换global new-handler.

    operator new具体做以下事情:

    1.调用标准set_new_handler,将class的专属new-handler安装为global new-handler.

    2.调用 global operator new,执行实际内存分配.如果分配失败,global operator new 会调用class的专属new-handler.

    如果global operator new最终无法分配足够内存,会抛出一个bad_alloc异常.在此情况下,需要确保原本的new-handler能够被恢复,因而参用对象管理new-handler.

    new-handler的恢复过程时,是由class的析构函数完成的,当对象脱离作用域时,调用析构函数,恢复原本的new-handler.

     1 class NewHandlerHolder {
     2 public:
     3     explicit NewHandlerHolder(std::new_handler nh)
     4         :handler(nh){}
     5     ~NewHandlerHolder()
     6     {
     7         std::set_new_handler(handler);
     8     }    
     9 private:
    10     std::new_handler handler;
    11     NewHandlerHolder(const NewHandlerHolder&);
    12     NewHandlerHolder& operator=(const NewHandlerHolder&);
    13 };
    14 template<typename T>
    15 class NewHandlerSupport {
    16 public:
    17     static std::new_handler set_new_handler(std::new_handler p) throw();
    18     static void* operator new(std::size_t size) throw(std::bad_alloc);
    19     ...
    20 private:
    21     static std::new_handler currentHandler;
    22 };
    23 template<typename T>
    24 std::new_handler
    25 NewHandlerSupport<T>::set_new_handler(std::new_handler p) throw()
    26 {
    27     std::new_handler oldHandler = currentHandler;
    28     currentHandler = p;
    29     return oldHandler;
    30 }
    31 
    32 template<typename T>
    33 void* NewHandlerSupport<T>::operator new(std::size_t size)
    34 {
    35     NewHandlerHolder h(std::set_new_handler(currentHandler));
    36     return ::operator new(size);
    37 }
    38 
    39 template<typename T>
    40 std::new_handler NewHandlerSupport<T>::currentHandler = 0;
    41 
    42 class Widget: public NewHandlerSupport<Widget> {
    43     ...
    44 };

    了解了编译器自身的new和delete实现机制,我们需要开始定制new和delete.

    定制过程中,为了保证定制的new和delete的一致性,需要保证其遵守一些规则.

    1.operator new必须得返回正确的值,内存不足时必须得调用new-handling函数.

    2.operator new必须得对付零内存需求的准备.

      编译器提供的operator new中,即使客户要求0bytes,operator new也得返回一个合法指针.定制的new也得合理的处理0byte需求.

    3.operator new必须得处理派生类中调用基类operator new的情况.

      处理方法是将内存申请量错误的调用行为改采标准operator new.

    1 void* Base::operator new(std::size_t size) throw(std::bad_alloc)
    2 {
    3     if(size != sizeof(Base))        
    4         return ::operator new(size);
    5     ...
    6 }

    4.operator delete需要保证"删除null指针永远安全".

     1 void Base::operator delete(void* rawMemory,std::size_t) throw()
     2 {
     3     if(rawMemory == 0)    return;
     4     if(size != sizeof(Base))
     5     {    
     6         ::operator delete(rawMemory);
     7         return;
     8     }
     9     ...
    10     return;
    11 }

    此外,定制new和delete应注意线程安全问题

    由于heap是一个可被改动的全局性资源,因此多线程系统充斥着访问这一类资源的race conditions(竞速状态)出现机会.

  • 相关阅读:
    外部无法捕捉Realm的doGetAuthenticationInfo方法抛出的异常
    Redis多实例及主从搭建
    Redis安装
    window.showModalDialog两次加载问题清除缓存方法
    JQuery分页插件bs_pagination的应用
    解决IE11出现异常SCRIPT5011:不能执行已释放Script的代码
    通过CSS禁用页面模块的复制和粘贴功能
    C# toString()转换详细(转)
    百度ue富文本编辑器setContent方法报错初始化加载内容失败解决办法
    前端页面div float 后高度 height 自适应的问题
  • 原文地址:https://www.cnblogs.com/dwdxdy/p/2613350.html
Copyright © 2011-2022 走看看