Effective_C++:7、为内存不足的状况预做准备

7、为内存不足的状况预做准备

1、使用new时内存不足

        当operator new无法配置内存时,会抛出一个异常exception(过去会传回0,部分老旧编译器可能仍是如此),std::bad_alloc即是抛出的异常,继承于exception。尽管内存不足并不是软件编程的问题,但是执行代码时,很有可能遇到这样的问题,故使用operator new时需要为此做准备。

2、如何处理内存不足

        常见的C习惯做法可能是定义一个宏:
#define NEW(PTR, TYPE) \
    try {(PTR) = new TYPE;}\
    catch(std::bad_alloc&) {assert(0);}
        如上,assert()在cassert头文件,是一个宏,只有在debug模式下才有用,会检查接收的算式结果是否为非0值,如果不是会发出错误信息并调用abort。但是,在非debug模式下也有可能发生这种情况,且上述只针对new的一种形式,即new T。
        事实上,可以建立一个简单的错误处理策略:当operator new不能申请到内存时,在抛出异常前会调用一个专属的错误处理函数,称为new_handler。为了指定这个错误处理函数,需要调用set_new_handler()函数,在new头文件中定义。
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
        如上,new_handler是一个typedef,是一个函数指针,指向的函数没有参数没有返回值。而set_new_handler是一个函数,入参与返回值均为new_handler,入参指向内存不足时调用的函数,返回值指向之前的new_handler。可以这样使用set_new_handler():
void noMoreMemory()
{
    cerr << "Unable to satisfy request for memory\n";
    abort();
}

int main()
{
    set_new_handler(noMoreMemory);
    int *pBigDataArray = new int[100000000];
    ...
}
        当operator new无法配置内存时,他会不停地调用new_handler,直到找到内存。故一个良好的new_handler必须完成以下事情之一:
        1.让更多内存可用。在程序起始时配置一块内存,当调用new_handler时释放内存并提醒用户内存不足,下次申请可能失败等。
        2.安装一个不同的new_handler。或许他知道别的new_handler握有较多资源,则安装新的new_handler,即调用set_new_handler一次。则下次operator new时,会调用新的new_handler。
        3.卸除这个new_handler,即将NULL指针传给set_new_handler,没有安装new_handler。当operator new配置内存失败时,会抛出型为std::bad_alloc的异常。
        4.抛出一个exception,型为std::bad_alloc或其派生类。这样的exception不会被operator new捕捉,所以他们会传送到最初提出内存需求的点上。
        5.不回返,直接调用abort或exit。他们是C标准函数库里的函数。

3、class专属的operator new

        C++不支持class专属的new_handler,即在各个class中加入同名的new_handler,这是不可行的。但是C++支持class专属的operator new和set_new_handler。如下,在类中加入了static类成员。
class X
{
public:
    static new_handler set_new_handler(new_handler p);
    static void * operator new(size_t size);

private:
    static new_handler currentHandler;
}

        为class X处理内存配置失败的问题,应将这些设为类的static成员,初始化在定义之外,如可以在实现文件中。

new_handler X::currentHandler;//初始化为0, 即NULL

        set_new_handler将获得的new_handler保存下来,返回之前保存的new_handler:

new_handler X::set_new_handler(new_handler p)
{
    new_handler oldHandler = currentHandler;
    currentHandler = p;
    return oldHandler;
}

        最后,operator new完成这些事情:

        1.调用std::set_new_handler,将class的new_handler设为新的new_handler,将原来的new_handler保存为globalHandler。

        2.调用::operator new,若内存配置失败,调用设置好的new_handler,若最终还是没有配置内存,则抛出std::bad_alloc的异常,被X::operator new捕捉,调用std::set_new_handler,将new_handler恢复为globalHandler保存的new_handler,再将异常传播出去并结束函数。

        3.若::operator new内存配置成功,同样调用std::set_new_handler,将new_handler恢复为globalHandler保存的new_handler。然后传回一个指针,指向那块内存。

void * X::operator new(size_t size)
{
    new_handler globalHandler = std::set_new_handler(currentHandler);
    void *memory;
    try
    {
        memory = ::operator new(size);
    }
    catch(std::bad_alloc&)
    {
        std::set_new_handler(globalHandler);
        throw;
    }
    std::set_new_handler(globalHandler);

    return memory;
}

        以上,事实上,可以添加模板来实现代码的重用,只需添加template<typename T>,修改类型即可。然后定义的类由此模板类派生即可。这样的操作方便简单,但有可能引入多重继承的一些问题,这里暂且不提。

        因此,使用operator new时务必关注内存配置失败的问题,处理此问题的方法之一是判断返回值是否为0,这对于抛出异常形式的new会测试失败;而另外方法之一是用set_new_handler,他对于抛出异常形式的new和nothrow形式的new都适用。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章