• effective C++ 条款 49:了解newhandler的行为


    当operator new无法满足某一内存分配需求时,会抛出异常。再抛出异常以反映一个未获满足的内存需求之前,它会先调用客户指定的错误处理函数,new-handler。为了指定这个“用以处理内存不足”的函数,客户必须调用set-new-handler,那是声明于<new>的一个标准函数库函数:

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

    thow()是一份异常明细,表示函数不会抛出任何异常。

    set_new_handler参数是一个指针指向operator new无法分配足够内存时该被调用的函数。返回指向set_new_handler被调用前正在执行的那个new_handler函数。

    void outOfMem()
    {
        std::cout << "Unable to satisfy request for memory\n";
        std::abort();
    }
    int main()
    {
        std::set_new_handler(outOfMem);
        int* pBigDataArray = new int[1000000000000L];
    }

    一个设计良好的new_handler必须做以下事情:

    1.让更多内存可使用。造成operator new内的下一次内存分配动作可能成功。一个做法是,程序开始执行就分配一大块内存,在new-handler第一次被调用,将它们释还给程序使用。

    2.安装另一个new-handler。如果这个new-handler无法取得更多可用内存,或许它知道另外有个new-handler有此能力。目前这个就可以安装另外那个以替换自己(只需调用set_new_handler)。下次当operator new调用那个new-handler,调用的将是最新安装的那个。(这个旋律的变奏之一就是让new-handler修改自己的行为,于是当他下次被调用就会做些不同的事。为达此目的,做法之一就是令new-handler修改“会影响new-handler行为”的static数据、namespace数据、或global数据。)

    3.卸除new-handler。就是将null指针传给set_new_handler。一旦没有安装任何new_handler,operator new会在内存分配不成功时抛出异常。

    4.抛出bad_alloc(或派生自bad_alloc)的异常。这样的异常不会被operator new捕获,因此会传到内存索求处。

    5.不反回。通常调用abort或exit。

    有时你想以不同方式处理内存分配失败情况,你希望视被分配物属于哪个class而定:

    c++并不支持class专属之new-handler,其实也不需要。你可以自己实现出这种行为。只需令每一个class提供自己的set_new_handler和operator new即可。其中set_new_handler使客户指定class专属的new-handler。

    现在,假设你打算处理Widget class 的内存分配失败情况。

    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;
    };

    static成员必须在class定义之外定义(除非它们是const而且是整数型):

    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做以下事情:

    1.调用标准set_new_handler,告知Widget的错误处理函数,这回将Widget的new_handler安装为global new-handler。

    2.调用global operator new,执行实际的内存分配。如果分配失败,global operator new会调用Widget的new_handler,才刚被安装为global new-handler。如果global operator new最终无法分配足够内存,会抛出一个bad_alloc异常。此情况下Widget的operator new必须恢复原本的global new_handler,然后再传播该异常。为确保原本的new_handler总是能够被重新安装回去,Widget将global new_handler视为资源并遵守条款13忠告,运用资源管理对象防止资源泄漏。

    3.如果global operator new能够分配足够一个Widget对象所用的内存,Widget的operator new会返回一个指针,指向分配所得。Widget 析构函数会管理global new_handler,它会自动将widget’s operator new被调用前的那个global new_handler恢复回来。

    class NewHandlerHolder{
    public:
        explicit NewHandlerHolder(std::new_handler nh)
            :handler(nh){}                                                            //取得目前的new_handler
        ~NewHandlerHolder()
        {
            std::set_new_handler(handler);                                    //释放它
        }
    private:
        std::new_handler handler;                                            //记录下来
        NewHandlerHolder(const NewHandlerHolder&);            //阻止copying
        NewHandlerHolder& operator=(const NewHandlerHolder&);
    };

    这就使得Widget’s operator new的实现相当简单:

    void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
    {
        NewHandlerHolder h(std::set_new_handler(currentHandler)); //安装 Widget的new_handler
        return ::operator new(size);                                        //分配内存或抛出异常。
    }                                                                                    //恢复global new_handler

    Widget的客户应该这样使用其new_handling:

    void outOfMem();
    Widget::set_new_handler(outOfMem);
    Widget* pw1 = new Widget;                    //如果内存分配失败,调用outOfMem
    std::string* ps = new std::string;                //如果内存分配失败,调用global new_handling函数
    Widget::set_new_handler(0);
    Widget* pw2 = new Widget;                    //如果内存分配失败,立刻抛出异常,new_handling为null

    实现这一方案的代码并不因class的不同而不同,因此在它处加以复用是个合理的构想。一个简单的做法是建立一个mixin风格的base class,这种base class允许derived classes继承单一特定能力。然后将这个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 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 new(std::size_t size)
    {
        NewHandlerHolder h(std::set_new_handler(currentHandler));
        return ::operator new(size);
    }

    以下将每个currentHandler初始化为null

    template<typename T>
    std::new_handler NewHandlerSupport<T>::currentHandler = 0;

    为Widget添加set_new_handler支持能力就轻而易举了:只要Widget继承自NewHandlerSupport<Widget>就好。

    class Widget: public NewHandlerSupport<Widget>{
        ...                //和先前一样,但不必声明set_new_handler或operator new
    };

    实际上使用template T只是希望, 继承自NewHandlerSupport的每个class,拥有实体互异的NewHandlerSupport复件(更确切的说是其static成员变量currentHandler)。类型参数只是用来区分不同的derived class。template机制会自动为每一个T(NewHandlerSupport赖以具现化的根据)生成一份currentHandler

    Widget继承自一个模板化的base class,而后者又以Widget作为类型参数:“怪异的循环模板模式”(curiously recurring template pattern;crtp)。像是do it for me.

    1993年之前,operator new在无法分配足够内存时返回null。新一代的operator new 抛出 bad_alloc异常。

    class Widget{...};
    Widget* pw1 = new Widget;
    if (pw1 == 0)...                        //这个测试一定失败,因为如果分配失败,抛出bad_alloc
    Widget* pw2 = new(std::nothrow) Widget;
    if (pw2 == 0)...                        //这个测试可能成功,如果分配Widget失败,返回0

    nothrow new是个颇为局限的东西,因为它只适用于内存分配,后续的构造函数还是可能抛出异常。如果构造函数又new一些东西,没人强迫它再次适用nothrow new。

  • 相关阅读:
    ioctl函数用法小记
    scanf函数用法小记
    printf函数用法小记
    REDIS
    lspci 虚拟机网卡对应关系
    vmware安装ubuntu " Intel VT-x 处于禁用状态"
    win10远程桌面配置
    Win10如何彻底禁用小娜?彻底禁用小娜的方法
    为什么Windows7打开项目的方式是灰的不能修改
    以下suse11.3x64可以安装pycrypto-2.6.1
  • 原文地址:https://www.cnblogs.com/lidan/p/2357698.html
Copyright © 2020-2023  润新知