《Effective C++》讀書筆記———定制new與delete


定制new和delete

第49條:了解new_handler的行為

什么是new_handler?

  1. new_handler是一個函數指針類型,定義在 頭文件中,它為函數std::set_new_handler()與std::get_new_handler()所使用. 原型定義如下所示:
typedef void (*new_handler)()

使用new_handler定義了一個全局new的處理函數指針對象,例如 new_handler g_new_handle_func = nullptr. 設計該函數的作用是:當new操作符進行內存分配失敗時會調用該函數, 希望它可以做下面三件事之一:

  • 該函數可以通過某些操作,獲取更多可用的內存,從而使下一次new操作可能成功。
  • 該函數可以選擇終止程序, 因為內存不夠使用了,再執行下去可能沒有意義了。
  • 該函數可以選擇拋出 std::bad_alloc 的異常。
  1. std::set_new_handler()函數定義在 頭文件中, 它的作用是設置新的全局new的處理函數並返回先前安裝的new處理函數。
  2. std::get_new_handler()函數定義在 頭文件中,它的作用是返回當前安裝的new的處理函數,它有可能返回空指針的,表示沒有處理函數。

new_handler的處理過程

  1. 當new操作符申請內存時,如果發現沒有足夠多的內存可以使用時,它會去檢查new_handler處理函數是否為nullptr, 如果為空,則拋出異常;當不為空時,它會調用該處理函數。
  2. 如果new處理函數拋出異常或終止程序時,程序就會終止掉了。
  3. 如果new處理函數正常返回(例如它可以嘗試通過一系列的努力從其它地方找出來一些內存),new操作符就會又繼續重復一開始的內存申請操作了。

特別說明:如果你自定義的new處理函數沒有做上面說的三件事之一,當內存不足時,你的代碼就會進行死循環了.

不合理的new處理函數說明:

#include <iostream>
#include <new>
using namespace std;

void my_new_handle()
{
    cout << "現在死循環中...\n" << endl;
}

int main()
{
    new_handler old_handle = set_new_handler(my_new_handle);
    unsigned int size = 1 << 31;
    long *p = new long[size];
    return 0;
}

第50條:為什么有時需要替換c++原有的new與delete操作符呢?

  1. 通過重載new/delete操作符可以用於檢測一些關於內存操作的錯誤信息,例如可以實現內存泄漏的小工具。
  2. 可以加強專屬的new/delete的性能。標准庫提供的new/delete具體通用性,對於某些場景可以定義自己的new/delete運算符。

第51條:重載new與delete運行符時的注意事項

  1. operator new內應該含有一個無窮循環,在循環內進行內存分配,如果內存不足時就應該調用new_handler處理函數。
  2. c++規定安申請0字符的內存時也應該返回一個合法的指針,因此operator new操作符應該處理這種情況。
  3. 重載operator delete操作符時,記住一點:c++保證刪除NULL指針永遠是安全的。 所以你要處理這種特殊情況。

第52條:placement new 與 palcement delete版本

如果operator new 接受的參數除了一定會有的size_t之外,還有其它的參數,則認為這就是所謂的 placement new. 通常使用的placement new 是一個接受一個指針指向對象該被構造之處,表示從指針所指的地方進行內存申請。 這樣版本的new已經納入了c++標准程序庫里了,它的格式如下所示:

void* operator new(std::size_t, void* pMemory) throw();

當然,也可以定義其它形式的placement new, 例如下面所示:

void* operator new(std::size_t size, const string& info) {
    cout << info;
    return ::operator new(size);
}

我們知道,當我們寫一個new表達式時,共有兩個函數被調用:一個是用於內存分配的operator new, 另一個是對象的構造函數。如果在第一步,內存申請成功了,然后在執行對象的構造函數時被拋出了異常,如果不處理申請到的內存就會造成內存泄漏問題。 編譯器會這么做:它會查找到與oeprator new函數的參數相同的oeprator delete的函數,然后調用該operator delete進行內存的釋放,如果沒有找到對應的函數編譯器什么也不做。 因此,如果你想自定義placement new函數的話,你也需要定義一個對應的placement delete函數,該函數只會在new一個對象時如果內存申請成功但是構造失敗的時候調用,用於防止內存泄漏, 正常情況下是不會被調用的。舉例說明:

#include <iostream>
#include <new>
#include <exception>

using namespace std;

void* operator new(std::size_t size, int)
{
    std::cout << "placement new is called. " << std::endl;
    return ::operator new(size);
}

void operator delete (void* ptr)
{
    std::cout << "regular operator delete is called." << std::endl;
}

void operator delete(void* ptr, int)
{
    std::cout << "placement delete is called. " << std::endl;
}

class Obj {
public:
    Obj() { throw std::bad_alloc(); };
};


class NoThrowObj {
public:
    NoThrowObj() {};
};

int main()
{
    try {
        // 在new Obj時,它的構造函數拋出異常,並且會調用placement delete 進行內存的釋放。
        Obj* p = new (100) Obj;
    } catch (...) {
        std::cout << "已經捕獲。" << std::endl;
    }

    // 在進行delete時,常規的operator delte 被調用。
    NoThrowObj* p = new (1000) NoThrowObj;
    delete p;

    return 0;
}

輸出為:

placement new is called. 
placement delete is called. 
已經捕獲。
placement new is called. 
regular operator delete is called.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM