zoukankan      html  css  js  c++  java
  • 读书笔记_Effective_C++_条款四十九:了解new_handler的行为

    https://www.cnblogs.com/jerry19880126/p/3722531.html

    本章开始讨论内存分配的一些用法,C/C++内存分配采用new和delete。在new申请内存时,可能会遇到的一种情况就是,内存不够了,这时候会抛出out of memory的异常。有的时候,我们希望能够调用自己定制的异常处理函数,这就是本条款要说的。

    在声明于<new>的一个标准程序库中,有如下的接口:

    1 namespace std
    2 {
    3     typedef void (*new_handler)();
    4     new_handler set_new_handler(new handler p) throw();
    5 }

    注意这里面typedef了一个函数指针new_handler,它指向一个函数,这个函数的返回值为void,形参也是void。set_new_handler就是将new_handler指向具体的函数,在这个函数里面处理out of memory异常(函数末尾的throw()表示它不抛出任务异常),如果这个new_handler为空,那么这个函数没有执行,就会抛出out of memory异常。

    复制代码
     1 void MyOutOfMemory()
     2 {
     3     cout << "Out of memory error!" << endl;
     4     abort();
     5 }
     6 
     7 int main()
     8 {
     9     set_new_handler(MyOutOfMemory);
    10     int *verybigmemory = new int[0x1fffffff];
    11     delete verybigmemory;
    12 }
    复制代码

    这里预先设定好new异常时调用的函数为MyOutOfMemory,然后故意申请一个很大的内存,就会走到MyOutOfMemory中来了。

    好,我们更进一步,现在想要在不同的类里面定制不同的new_handler处理机制,一种想法是在类内部定义set_new_handler函数,将new_handler作为私有的成员变量,具体的new_handler函数可以由构造函数传入,但编译器要求set_new_handler是静态的,所以通过构造函数传入new_handler不被编译器支持,只能将set_new_handler与operator new都写成静态的,同时定义一个静态的new_handler变量,像下面这样:

    复制代码
     1 class Widget
     2 {
     3 private:
     4     static new_handler CurrentHandler;
     5 
     6 public:
     7     void set_new_handler(new_handler h) throw()
     8     {
     9         CurrentHandler = h;
    10     }
    11     static void* operator new(size_t size)
    12     {
    13         Widget::set_new_handler(CurrentHandler);
    14         return ::operator new(size);
    15     }
    16 };
    17 
    18 new_handler Widget::CurrentHandler = 0;
    复制代码

    属于类的静态变量CurrentHandler用于保存当前环境下的new_handler函数,在operator_new中,先设置成当前的new异常处理函数,再去调用std的operator new,执行内存分配操作。但这里就存在问题了,set_new_handler到下一次设置它为止,一直都是生效的,我们只想在处理这个类对象的分配时用自定义的new_handler函数,但是类似于new int,new char这些基本类型,还是希望走默认的new_handler(就是null,就是什么也不执行,如我们期望,这样会抛出异常)。

    一种自然的想法,就是在调用operator new末尾处还原new_handler,这就需要保存之前的new_handler,为此,我们构造一个NewHandlerHolder类,像下面这样:

    复制代码
     1 class NewHandlerHolder
     2 {
     3 private:
     4     new_handler SavedHandler;
     5     NewHandlerHolder(const NewHandlerHolder&);
     6     NewHandlerHolder& operator= (const NewHandlerHolder&);
     7 
     8 public:
     9     explicit NewHandlerHolder(new_handler h) :SavedHandler(h){}
    10     ~NewHandlerHolder()
    11     {
    12         set_new_handler(SavedHandler);
    13     }
    14 };
    复制代码

    这里有一个SavedHandler成员变量,它在NewHandlerHolder构造时确定具体的指向(其实就是指向系统默认的new_handler函数(即null),将拷贝构造函数与赋值运算符重载设置为private是为了防止出现拷贝的行为(在编译期就可以阻止),这点可以参照之前的条款。还要特别注意这里的析构函数,它调用了set_new_handler,将new异常的处理恢复成SavedHandler(其实就是null)。

    这样我们重新整理一下Widget,如下:

    复制代码
     1 class Widget
     2 {
     3 private:
     4     static new_handler CurrentHandler;
     5 
     6 
     7 public:
     8     static new_handler set_new_handler(new_handler h) throw()
     9     {
    10         new_handler OldHandler = CurrentHandler;
    11         CurrentHandler = h;
    12         return OldHandler;
    13     }
    14     static void* operator new(size_t size)
    15     {
    16         NewHandlerHolder h(Widget::set_new_handler(CurrentHandler));
    17         return ::operator new(size);
    18     }
    19 };
    20 
    21 new_handler Widget::CurrentHandler = 0;
    复制代码

    为了返回系统默认的new_handler,我们在set_new_handler处理完之后,进行了旧handler的返回,同时在operator new的调用中进行了NewHandlerHolder的包装,这样在return之后,h会自动调用析构函数,恢复成默认的new_handler。

    到这一步,本条款的重要内容已经说完了,但为了避免重复劳动,即为每一个需要重定义new_handler的类都写一份set_new_handler和operator new,书上在最后对之进行了封装,其实就是将Widget专门作为这两个函数(set_new_handler和operator new)的类,然后将需要自行处理new_handler的类public继承于Widget即可。

    最后总结一下:

    set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。

    1。global operator new( ::operator new )一般是被这样实现的:
    void *operator new(size_t size)
    {
    if (size == 0)
    size = 1;
    void *last_alloc;
    while (!(last_alloc = malloc(size)))
    {
    if (_new_handler)
    (*_new_handler)();
    else
    throw std::bad_alloc(); // Or return 0;
    }
    return last_alloc;
    }

    2。global set_new_handler()( ::set_new_handler() )应该是这样实现的:
    new_handler set_new_handler(new_handler p)
    {
    old_handler = _new_handler;
    _new_handler = p;
    return old_handler;
    }
    具体的实现在c runtime library里可以找到。
    这样利用_new_handler完成了他们之间的沟通。

    2。在你说的这种情况中:
    class x {
    public:
    static new_handler set_new_handler(new_handler p);
    static void * operator new(size_t size);
    private:
    static new_handler currenthandler;
    };

    x::set_new_handler()和x::operator new()都会模仿global的版本行为,
    并把实际操作转发给global版本:

    new_handler x::set_new_handler(new_handler p)
    {
    new_handler oldhandler = currenthandler;
    currenthandler = p;
    return oldhandler;
    }

    void * x::operator new(size_t size)
    {
    new_handler globalhandler = // 安装x的new_handler    
    std::set_new_handler(currenthandler);

    void *memory;

    try {  // 尝试分配内存
    memory = ::operator new(size); // !!这个地方有可能调用你的new_handler(currenthandler);请参考上面::operator new()的实现。
    }
    catch (std::bad_alloc&) {  // 恢复旧的new_handler
    std::set_new_handler(globalhandler);      
    throw; // 抛出异常
    }
    std::set_new_handler(globalhandler); // 恢复旧的new_handler
    return memory;
    }

    仔细研究一下,当你写下下面这些代码时将发生什么事情:

    void nomorememory();
    // x的对象分配内存失败时调用的new_handler函数的声明

    x::set_new_handler(nomorememory);
    // 把nomorememory设置为x的new-handling函数
    // 此时x::currenthandler为nomorememory

    x *px1 = new x; 
    // new x;这句将有两个动作:
    // 1。调用x::operator new分配内存,这是因为x中的operator new定义屏蔽了
    // ::opertor new的定义(注意:operator new只分配内存,并不构造实例)
    // 2。调用x::x()构造实例,这部分工作由编译器完成。   
    // 对1。参考上面x::operator new的工作过程,可以知道,它是如何利用::set_new_handler
    // 把x::currenthandler设置成全局的_new_handler,并在完成工作后恢复它的。
    // 而在设置之后,x::operator new会调用::operator new,在::operator new的
    // 工作过程中,如果分配失败,会调用全局的_new_handler(参考::operator new的实 
    // 现),而此时它正是x::currenthandler。

    综上,我前帖中说: 
    “在重载的operator new操作中又是如何 调用new-handler的”
    operator new不会调用new-handler
    ---------------
    个人以为是没有错的。它只是简单的把它移交给::operator new。

    3。关于“任何版本的operator new都只分配内存,不构造实例。”
    这就是说写 x *p = new x; 并不是只调用x::operator new。
    看看这个例子:
    struct test
    {
    test(int i = 1) : i_(i) {}
    int i_;

    static void *operator new(size_t size)
    {
    cout << "test::operator new called" << endl;
    return ::operator new(sizeof(test));
    }
    };

    void main()
    {
    test *p = new test;
    cout << p->i_ << endl; 
    p = (test*)test::operator new(sizeof(test));
    cout << p->i_ << endl; 
    }

    运行结果:
    test::operator new called
    1
    test::operator new called
    -842150451

  • 相关阅读:
    JAVA学习25天
    Java学习第24天
    Java学习第23天
    Java学习22天
    第3周
    Java21
    day23作业
    day23
    Typecho使用技巧
    搭建Typecho博客
  • 原文地址:https://www.cnblogs.com/invisible2/p/8642171.html
Copyright © 2011-2022 走看看