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都適用。


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