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

本文探讨了C++中new-handler的功能及实现方式,包括如何自定义new-handler以处理内存分配失败的情况,并介绍了nothrownew的特点及其局限性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

    条款49:了解new-handler的行为
    Understand the behavior of the new-handler.
   
    在我们平时的编写代码过程中,总是避免不了去new出一些对象出来.我们知道new操作符私底下通过调用
operator new来实现内存分配的.当operator new抛出异常以反映一个未获满足的内存需求之前,它会调用一
个客户指定的错误处理函数,一个所谓的new-handler.而客户是通过set_new_handler将自己的new-handler传
递给它的,其声明在<new>标准库函数中:
    namespace std{
        typedef void (*new_handler)();
        new_handler set_new_handler( new_handler p ) throw();
    }
    当operator new无法满足内存申请时,它会不断调用new-handler函数,直到找到足够内存.关于反复调用
代码我们在条款51中我们会讨论.一个设计良好的new-handler函数必须做以下事情:
    ■ 让更多内存可被使用.
    ■ 安装另一个new-handler.
    ■ 卸除new-handler
    ■ 抛出bad_alloc(或派生自bad_alloc)的异常.
    ■ 不返回.
    这些选择让你在实现new-handler函数时拥有很大的弹性.而有时候你会希望'依据不同的类,调用附属
与该类的new-handler,看起来也许是这个样子:
    struct TestA{
        static void outOfMemory();
        ...
    };
    struct TestB{
        static void outOfMemory();
        ...
    };
    TestA* a = new TestA;//if memory runs out,call TestA::outOfMemory
    TestB* b = new TestB;//if memory runs out,call TestB::outOfMemory   
    但是C++不支持class专属之new-handlers,但你可以自己实现这种行为.好,我们来试试!
    假设我们现在打算处理Widget class的内存分配失败情况,我们需要声明一个类型为new_handler的static
成员,用以指向class Widget的new-handler,看起来应该像这样:
    struct Widget{
        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 st_current_handler_;
    };
    Widget内的set_new_handler函数会将它获得的指针存储起来,然后返回调用之前的指针,这跟标准版
set_new_hander的行为是一样的:
    std::new_handler Widget::set_new_handler( std::new_handler p ) throw (){
        std::new_handler old_handler = st_current_handler_;
        st_current_handler_ = p;
        return old_handler;
    }
    下面我将构造一个资源处理类来操作new-handler,在构造过程中获得资源,在析构过程中释放:
    struct NewHandlerHolder{
        explicit NewHandlerHolder(std::new_handler handler)
            :handler_( handler ){}
        ~NewHandlerHolder(){
            std::set_new_handler( handler_ );
        }
    private:
        NewHandlerHolder(const NewHandlerHolder&);
        NewHandlerHolder& operator=(const NewHandlerHolder&);
        std::new_handler handler_;       
    };
    这样Widget内的operator new的实现就变的容易了:
    void* Widget::operator new(std::size_t size) throw(std::bad_alloc)
    {
        NewHandlerHolder alloc_handler( std::set_new_handler( st_current_handler_ ) );
        return ::operator new( size );
    }
    客户现在可以这样用:
    void outOfMemory();  
    Widget::set_new_handler( outOfMemory );
    Widget* new_widget_obj = new Widget; //if memory allocation failed,
                                      //call outOfMemory
    std::string* new_string = new std::string; //if memory allocation failed,
                                            //call global new-handling function
                                            //if exists.                                              
    Widget::set_new_handler( 0 );
    Widget* another_widget = new Widget; // if memory allocation failed,throw
                                      //exception at once.                                     
    其实我们可以用base class将new-handler操作独立出来,让Widget继承自base class,呵呵,这样是
不是更好:
    template<typename T>
    struct NewHandlerSupport{
        static std::new_handler set_new_handler( std::new_handler p ) throw (){
            std::new_handler old_handler = st_current_handler_;
            st_current_handler_ = p;
            return old_handler;
        }
        static void* operator new( std::size_t size ) throw ( std::bad_alloc ){
            NewHandlerHolder h( std::set_new_handler( st_current_handler_ ) );
            return ::operator new( size );
        }
        ...
    private:
        static std::new_handler st_current_handler_;
    };
    template<typename T>
    std::new_handler NewHandlerSupport<T>::st_current_handler_ = 0;
    有了这个template,为Widget添加set_new_handler支持能力就轻而易举了:
    struct Widget:public NewHandlerSupport<Widget>{
        ...
    };
    好了,这个问题的讨论,就结束了.我们来把目光转到Nothrow new.
    nothrow版的operator new被调用,用以分配足够内存给Widget对象.如果分配失败便返回null指针,
一如文档所言.所有分配成功,接下来Widget构造函数会被调用,而在那一点上所有的筹码便被耗尽,因为
Widget构造函数可以做它想做的任何事情.它有可能又new一些内存,而没人可以强迫它再次使用nothrow
new.现在我们可以得出结论:使用nothrow new只能保证operaor new不抛掷异常,不保证像'new (std:
:nothrow) Widget'这样的表达式不导致异常,其实你没有运用nothrow new的需要.
    今天的讨论就到这里,我们下款再见!
    请记住:
    ■ set_new_handler允许客户指定一个函数,在内存分配无法获得满足时调用.   
    ■ nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能
    抛出异常.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值