定制自己的new和delete:operator new 和 operator delete


new和delete不同用法


基本用法

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

上面的代碼是我們最基本也是最常見的使用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 delete

全局的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);這個函數

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 ???因為它的用途不在這,而在於下面將要講的。

針對類定制版本的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的構造函數中,拋出一個異常(忽略什么異常,主要用來模擬),直接上運行結果圖:

如上圖所示的運行結果,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);

若是不給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++》,若哪里有理解不對,請大家多指出。


免責聲明!

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



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