定製本身的new和delete:operator new 和 operator delete

new和delete不一樣用法


基本用法

int * aptr = new int(10);
delete aptr, aptr = nullptr;ios

上面的代碼是咱們最基本也是最多見的使用new和delete的方式,當編譯器運行int * aptr = new int(10); 這行代碼時,實際上是分爲兩個步驟來執行,第一步,調用operator new(size_t size) 分配內存;第二步在分配的內存上調用placement new(void * ptr) T(); 「定位放置 new」,就是把對象構建在指定的ptr指向的內存上,換句話就是在指定的內存上調用構造函數。數組

概念區分

new operator 和 delete operator :new 和 delete 操做符(關鍵字),沒法重載
operator new 和 operator delete:兩個函數用來服務於 new 和 delete 操做符,以及對應的 operator new [] , operator delete [] 對應於 new [] 和 delete []相關的數組操做;這兩個函數是能夠被重載的,通常有全局默認的函數,本身也能夠定義本身的,在定義C++類的時候也能夠爲某個class定製對應的 operator new 和 operator deleteide

全局的operator new 和 operator delete函數

全局默認operator new 函數:函數

void * operator new(std::size_t count) throw(std::bad_alloc); //normal new
void * operator new(std::size_t count, void *ptr)throw(); //placement new
void * operator new(std::size_t count, const std::nothrow_t&)throw();//nothrow new, 爲了兼容之前的版本

咱們能夠根據本身的意願來重載 不一樣版本的operator new 函數來覆蓋掉全局的函數,對於申明的類,能夠在類中申明對應的static void * operator new(size_t size); 來爲該類定製本身的operator學習

operator new的不一樣重載辦法

#include <iostream>
#include <new>
#include <cstring>
#include <cstdlib>
using namespace std;
/*
    首先本身定義operator new函數,來替代編譯器全局默認的operator函數
*/
void * operator new(size_t size){
    cout<<"global Override operator new"<<endl;
    void * ptr = malloc(size); //本身調用malloc來分配內存
    return ptr;
    //下面這句話會引發遞歸的調用,重載operator new以後,::operator new就等於調用本身
    //return ::operator new(size);
}
//重載版本的operator new,該函數默認的是調用 上面的operator new函數
void * operator new(size_t size, int flag){  
    cout<<"global Override operator new: "<<flag<<endl;
    return (::operator new(size));
}
//覆蓋掉全局的operator delete 函數
void operator delete (void * ptr){
    cout<<"global Override operator delete"<<endl;
    free(ptr);
    ptr = nullptr;
}
/*
    重載版本的operator delete,該函數主要的用途是在構造函數執行不成功的時候,調用與new 函數對應的 delete來釋放,稍後會有對應的列子來介紹,在這個例子中該函數暫時沒用
*/
void operator delete (void * ptr, int flag){
    cout<<"Override operator delete: "<<flag<<endl;
    ::operator delete(ptr);
    ptr = nullptr;
}
int main(){
    int * ptr = new int(10);
 /*
    delete ptr; 調用的就是 void operator delete(void * ptr); 而與new 匹配的delete 不是本身調用的,而是在new申請,成功卻在構造函數時候出錯,new operator本身根據operator new 來尋找 對應的operator delete 來調用,稍後介紹。
 */
    delete ptr;
    cout<<endl<<"*********************"<<endl<<endl;
    ptr = new(20) int(10);
    delete ptr;
    return 0;
}

上面的程序的輸入以下面:測試

從上面的結果能夠看出,new int(10);直接先調用 operator new(size_t size); 因爲int沒有構造函數,在那塊內存上調用int的構造函數; 在delete ptr; 的時間直接調用 operator delete(void * ptr);這個函數this

new(20) int(10);的時候,則調用重載版本的 operator new(size_t size, int flag); 而該函數有調用了 operator new(size_t size); 函數,釋放的時候delete ptr;仍是直接只調用operator delete(void * ptr);(注:這裏初步提出爲啥不調用operator delete(void * ptr, int flag); 這個函數來釋放ptr ???由於它的用途不在這,而在於下面將要講的。spa

針對類定製版本的operator new 和 operator delete

#include <iostream>
#include <new>
#include <cstring>
#include <cstdlib>
using namespace std;
//下面的operator new 和 operator delete 和上面的代碼同樣,替代默認全局函數
void * operator new(size_t size){
    cout<<"global Override operator new"<<endl;
    void * ptr = mallo![](http://images2015.cnblogs.com/blog/1042615/201610/1042615-20161021150145638-80575599.jpg)
c(size);
    return ptr;
}
void * operator new(size_t size, int flag){ 
    cout<<"global Override operator new: "<<flag<<endl;
    return (::operator new(size));
}
void operator delete (void * ptr){
    cout<<"global Override operator delete"<<endl;
    free(ptr);
    ptr = nullptr;
}
//此次主要體現該函數的用法*********************
void operator delete (void * ptr, int flag){
    cout<<"Override operator delete: "<<flag<<endl;
    ::operator delete(ptr);
    ptr = nullptr;
}
class Base{
public:

    Base(){
        cout<<"Base construct"<<endl;
        throw 2;
    }
    /*
        類中定製的operator new會覆蓋全局的函數,但能夠經過簡單的調用全局的函數來實現調用
    */
    static void * operator new(size_t size){
        cout<<"operator new of Base"<<endl;
        return ::operator new(size); //調用全局的operator new
    }
    static void * operator new(size_t size, int flag){
        cout<<"Override operator new of Base: "<<flag<<endl;
        return operator new(size);
    }
    static void operator delete(void * ptr){
        cout<<"Operator delete of Base"<<endl;
        ::operator delete(ptr);
    }
    static void operator delete(void * ptr, int flag){
        cout<<"Override operator delete of Base: "<<flag<<endl;
        operator delete(ptr);
    }
    int x;
    int y ;
};
int main(){
    try{
        Base * bptr = new(20) Base;
    }
    catch(...){
        cout<<"catch a exception"<<endl;
    }
    return 0;
}

上面的函數,在Base的構造函數中,拋出一個異常(忽略什麼異常,主要用來模擬),直接上運行結果圖:
code

如上圖所示的運行結果,new(20) Base首先調用類中定製的 operator new(size_t size, int flag); 而後在調用 operator new(size_t size); 在調用全局的 operator new(size_t size);申請完內存以後,在調用類的構造函數,此時會拋出異常,這個時候,因爲調用的 operator new(size_t size, int flag);函數來申請內存,但構造函數失敗了,此時 new - operator (new 關鍵字,區分operator new)會調用和 operator new 相同參數的operator delete函數來釋放已經申請的內存,所以operator delete(void ptr, int flag) ,在調用operator delete(void ptr); 在調用全局的operator delete(void * ptr);orm

如果不給Base類重載 static void operator delete(void * ptr, int flag);這個函數,結果則以下圖:

這個例子就說明,不定製對應的operator delete(), 則毫不會調用默認的operator delete函數來釋放內存,這裏就會致使內存泄露。所以在爲某個class 定製 operator new函數的時候,若是重載了不一樣參數的operator new,應該定製對應版本的operator delete(); 這裏對應版本是參數是對應的;(這裏也就是 operator delete(void * ptr, int flag);的主要用途,當構造函數異常的時候,它負責清理已申請的內存)。

其餘知識點


std::new_handler

這是一個回調函數,主要用於 operator new申請內存不成功的時候調用,感受能夠類比於,信號處理函數,operator new申請不成功(接受到某個信號),調用該handler(觸發信號處理函數)。最主要的用途就是經過set_new_handler()來更換不一樣的處理函數。

類繼承中的operator new函數處理

假如咱們爲一個Base類,定製了本身的operator new,則Base的派生類Derived,確定也繼承了該函數,

Derived * dptr = new Derived;的時候,確定就調用了Base::operator new()函數,而派生類的大小通常和基類大小是不一樣的,所以這裏須要額外注意,不能調用基類的operator new();

對於這點解決辦法,1,在Derived 中重寫operator new函數;2,在Base類的operator new函數中添加一句話以下圖:

void * operator new(size_t size ){
  if(size != sizeof(Base))
    return ::operator new(size);
  /*其餘交給Base::operator new處理*/
}

operator new[] 和 operator delete[]

這兩個和operator new operator delete 用處基本一致,只是針對數組的,這裏就很少講。

額外的補充(可能有點繞口, 下面是純我的觀點,有哪裏不對,請你們指出,共同窗習)

void operator new(size_t size, void * ptr) noexcept; 這是C++11官方定義的placement new的格式,

該函數的主要做用是把把size大小的結構體放置在ptr指向的內存上,也就是在ptr指向的內存調用構造函數。

先說一個上面的正確的使用方法:

#include <iostream>
#include <new>
#include <cstring>
using namespace std; 
class Base{
public:
    Base():x(-1), y(-1){ cout<<"Construct of Base and my addr = : ["<<this<<"]."<<endl;  }
    static void * operator new(size_t size, void * ptr){
        cout<<"My placement new."<<endl;
        return ::operator new(size, ptr);
    }
    //Base類只佔用兩個int型的大小
    int x;
    int y;
};
static int arr[20]; //棧上的空間,用來分配Base
int main(){
    memset(arr, 0, sizeof(arr));
    cout<<"arr addr = ["<<arr<<"]."<<endl;
    Base * ptr = new(arr) Base();
    ptr = nullptr;
    cout<<arr[0]<<"  "<<arr[1]<<endl;
    return 0;
}

arr數組前兩個變成 -1。
上面的代碼,ptr沒有直接delete,而是直接賦值爲nullptr,爲何這麼作,由於調用delete ptr; 會直接錯誤,ptr指向的是棧地址空間,而delete釋放的是堆上的空間,所以會出錯。

另外一種狀況:假設arr就是堆上的地址空間,此時調用delete ptr,確定能成功,可是這裏會有兩種風險,一種是,delete ptr;釋放一次內存,delete arr;第二次釋放內存,錯誤;另外一種,假設ptr 是佔用的arr指向的內存的中間部分,你delete ptr;歸還給系統,可是 arr 這個時候該怎麼處理,這種狀況應該是堅定杜絕的,(具體狀況我沒測試,原理上確定是杜絕這種狀況出現:只歸還堆上連續空間的中間部分。)

上面說了那麼多,其實想表達一個意思,當調用 void * operator new(size_t size, void * ptr);不管其是否成功,或者接下來的構造函數是否成功,ptr內存都不該該釋放,應該交由,ptr誕生的地方來管理。所以對於該函數通常不須要申明對應的 operator delete() 來防止構造函數未成功時候來釋放內存(和「聲明placement new 的時候必定要聲明對應的 placement delete函數這個理論有點相反」)。

要是強行爲 void * operator new(size_t size, void * ptr); 聲明對應的operator delete ,方式以下:

void operator delete(void * ptr, void * ptr2);代碼以下:

#include <iostream>
#include <new>
#include <cstring>
using namespace std; 
class Base{
public:
    Base():x(-1), y(-1){ 
      cout<<"Construct of Base and my addr = : ["<<this<<"]."<<endl;  
      throw 2;  
    }
    static void * operator new(size_t size, void * ptr){
        cout<<"My placement new."<<endl;
        return ::operator new(size, ptr);
    }
    /*
        必須聲明爲 void * ptr1, void * ptr2 這種形式,聲明爲其餘參數,和上面的 operator new不匹配
    */
    static void operator delete(void * ptr1, void * ptr2){
        cout<<"My operator delete"<<endl;
    }
    int x;
    int y;
};
int arr[20];
int main(){
    try{
        memset(arr, 0, sizeof(arr));
        cout<<"arr addr = ["<<arr<<"]."<<endl;
        Base * ptr = new(arr) Base();
        ptr = nullptr;
        cout<<arr[0]<<"  "<<arr[1]<<endl;
    }
    catch(...){
        cout<<"catch one exception."<<endl;
    }
    return 0;
}

能夠看出圖中有 My operator delete 的顯示,若把 operator delete參數改爲其餘的,則沒法調用。

看到有地方把operator new(size_t size, int flag, ...);形式(也就是除了size_t 一個參數以外,還有自定義參數,也就是重載原來的operator new(size_t size); 都稱爲 placement new)我覺重載更直觀。

對於void * operator new(size_t size, int flag, ../*自定義參數*/.. ); 與之對應的operator delete以下:

void operator delete(size_t size, int flag, ../*自定義參數*/..);

也就是有上面的爲啥,operator delete(void * ptr1, void * ptr2);

上面的內容主要參考自《Effective C++》,若哪裏有理解不對,請你們多指出。

相關文章
相關標籤/搜索