zoukankan      html  css  js  c++  java
  • 【effective c++读书笔记】【第8章】定制new和delete(1)

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

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

    namespace std {
    	typedef void(*new_handler)();
    	new_handler set_new_handler(new_handler p) throw();  //异常声明,表示该函数不抛出任何异常
    }
    

    set_new_handler的参数是个指针,指向operator new无法分配足够内存时该被调用的函数。其返回值也是一个指针,指向set_new_handler被调用前正在执行的(但马上就要被替换)的那个new-handler函数。

    set_new_handler用法例子:

    #include<new>
    #include<iostream>
    using namespace std;
    
    void outOfMem(){
    	cerr << "Unable to satisfy request for memory
    ";
    	abort();
    }
    
    int main(){
    	set_new_handler(outOfMem);
    	int* pBigDataArray = new int[0x7fffffff/4];
    
    	system("pause");
    	return 0;
    }

    运行结果:


    2、当operator new无法满足内存申请时,会不断调用new-handler函数,直到找到足够内存。一个设计良好的new-handler函数必须做以下事情:

    a、让更多内存可被使用。一个做法是程序一开始就分配一大块内存,当new-handler第一次被调用,将它们释还给程序使用。

    b、安装另一个new-handler。如果目前这个new-handler函数无法取得更多可用内存,则可以安装另外那个new-handler替换自己(只要调用set_new_handler)。

    c、卸载new-handler,也就是将null指针传给set_new_handler。一旦没有安装任何new-handler,operator new会在内在分配失败时抛出异常。

    d、捕捉bad_alloc(或派生自bad_alloc)的异常。

    e、不返回,通常调用abort或exit。

    3、C++并不支持类专属之new-handlers,但我们可以自己实现出这种行为。只需令每一个类提供自己的set_new_handler和operator new即可。set_new_handler使客户可以指定类专属的new-handler(就像标准的set_new_handler允许客户指定global new-handler),operator new则确保在分配类对象内存的过程中以类专属之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:
    	//声明一个类型为new_handler的静态成员函数,用以指向该类的new-handler
    	static std::new_handler currentHandler;  
    };
    

    注意static成员必须在类外定义(除非是const且是int型),所以需要这么写:

    std::new_handler Widget::currentHandler = 0;

    set_new_handler函数将它获得的指针存储起来,然后返回先前存储的指针,这也是标准版set_new_handler的作为:

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

    最后,Widget的operator new做以下事情:

    a、调用标准set_new_handler,告诉该类的错误处理函数,这会将该类的new-handler安装为全局的new-handler。

    b、调用全局的operator new,执行实际的内存分配。如果分配失败,全局的operatornew会调用该类的new-handler,因为那个函数刚刚被安装为全局的new-handler。如果全局的new-handler最终无法分配足够内存,会抛出bad_alloc异常,在此情况下该类的operator new必须恢复原来的全局new-handler,然后再传播该异常。为确保原来的new-handler总是能够被重新安装,该类将全局new-handler视为“资源”并使用资源管理对象来防止资源泄漏。

    c、如果全局operator new能够分配足够的内存,会返回一个指针指向分配的地址。该类的析构函数会管理全局new-handler,它会自动将该类operator new被调用前的那个全局new-handler恢复回来。

    4、构造一个资源处理类来操作new-handler,那里只有基础性RAII操作,在构造过程中获得资源,在析构过程中释放:

    class NewHandlerHolder {
    public:
    	explicit NewHandlerHolder(std::new_handler nh) : handler(nh) {}  //取得目前的new-handler
    	~NewHandlerHolder() {//释放new-handler
    		set_new_handler(handler);
    	}  
    private:
    	std::new_handler handler;   //记录下来
    	NewHandlerHolder(const NewHandlerHolder&);   //阻止拷贝行为
    	NewHandlerHolder& operator= (const NewHandlerHolder&);
    };
    
    这使得类Widge的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);  //分配内存或抛出异常,恢复全局new-handler
    }
    
    Widget的客户应该类似如下方式new-handler
    void outOfMem();	//声明内存分配失败时的处理函数
    Widget::set_new_handler(outOfMem);	//将上面的函数设定为Widget的new-handler函数
    Widget* pw1 = new Widget;	//如果内存分配失败,调用outOfMem()
    std::string* ps = new std::string;//如果内存分配失败,调用全局的new-handler函数(如果有的话)
    Widget::set_new_handler(0);	//设定Widget专属的new-handler为空,失去专属版本
    Widget* pw2 = new Widget;	//如果内存分配失败,则抛出异常
    

    5、可以将4中例子加以复用,建立mixin风格的基类,它允许派生类继承单一特定能力——本例中是“设定该类专属的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 NewHandlerHolder<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) throw(std::bad_alloc){
    	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
    };
    

    6、因为之前的operator new在无法分配足够内存时会返回null。为了兼容,提供了另一种形式的operator new, 负责应传统的“分配失败时返回null”的行为,称为nothrow形式,因为他们在new的使用场合使用了nothrow对象。如下:

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

    Nothrow new对异常的强制保证性并不高。

    请记住:

    • set_new_handler允许客户指定一个函数,在内存分配无法获得满足时被调用。
    • Nothrow new是一个颇为局限的工具,因为它只适用于内存分配;后继的构造函数调用还是可能抛出异常。

    版权声明:本文为博主原创文章,未经博主允许不得转载。

  • 相关阅读:
    【2020Python修炼记】网络编程(三)socket编程之粘包
    2020python作业——scoket编程(一)
    月考二
    【2020Python修炼记】网络编程(二)socket编程之socket 的介绍+TCP/UDP
    Gym100889L
    poj3233
    upper_bound
    lower_bound
    Gym-102141E
    P1020 导弹拦截
  • 原文地址:https://www.cnblogs.com/ruan875417/p/4785432.html
Copyright © 2011-2022 走看看