本文目錄
專業名稱
new operator:new操作符,new表達式
operator new:new運算符
placement new:定位new
delete operator:delete操作符,delete表達式
operator delete:delete運算符
delete沒有placement版本 各個版本的解釋 1. new operator
new operator:new表達式
實際上隻是一個稱呼,是三個操作的集合。
- 申請記憶體(調用operator new)
- 在記憶體上調用構造函數
- 将申請的記憶體空間傳回
delete operator:delete表達式
delete operator也是一個稱呼,是兩個操作的集合。
- 調用析構函數
- 調用operator delete釋放空間
operator new/delete:new/delete運算符
-
分為::operator new與Class::operator new。一個是全局的,一個是類内部定義的。平時聽到的new與delete重載,就是重載這裡的new/delete。注意點如下。
-- class中的new與delete是隐式靜态的。也就是不管你有沒有聲明為static,它都是static的。
-- 非static成員在沒有執行個體存在的情況下是不能使用的。而當我們定義一個執行個體時,很明顯此時執行個體還沒有存在,這個時候不可能調用一個非static的成員,是以operator new/delete必須是static的。
-- static成員是不會隐式傳入this指針的,是以在operator new/delete中不能使用任何類的非靜态成員。
-- 非靜态的成員函數在編譯器會在argument list的第一個位置插入一個本身類型的this指針。是以一個函數名,參數清單與全局函數一模一樣的函數,也不會出現二義性問題。但是一個static成員函數,是不會隐私插入this指針的,是以當我們在class中重載new/delete時,客戶定義執行個體時需要确認是否在全局也定義了new,否則會産生二義性。舉個栗子。
//VS2013
class StringClass
{
public:
void *operator new(size_t size);
};
void *operator new(size_t size);
void main()
{
//1. 調用operator new申請記憶體
//2. 調用構造函數
//二義性,同時存在兩個可用的operator new
StringClass *p = new StringClass;
}
-- PS:關于函數比對規則,一開始接觸覺得很簡單,當某天觸發了bug的時候就會“書到用時方恨少”,這方面推薦兩個連接配接給大家。看這裡,還有這裡
- 關于operator new/delete标準庫提供了8個版本,這些版本的new/delete我們都是可以重載的。如下。
//八個版本中有2個new版本有可能抛出異常
void* operator new(size_t t);
void* operator new[](size_t t);
void* operator delete(void*)noexcept;
void* operator delete[](void*)noexcept;
//nothrow_t為空結構體,用于區分上面與下面運算符
void* operator new(size_t t,nothrow_t&)noexcept;
void* operator new(size_t t,nothrow_t&)noexcept;
void* operator delete(void *,nothrow_t&)noexcept;
void* operator delete[](void *,nothrow_t&)noexcept;
理論這麼多,下面先來個栗子。
//StringClass.h
//自己定義的一個類
#pragma once
#include<iostream>
using namespace std;
class StringClass
{
public:
StringClass(){
cout << "string class construtor" << endl;
}
~StringClass(){
cout << "string class destrutor" << endl;
}
void *operator new(size_t size){
cout << "Class::operator new(size_t) " << size << endl;
void *ret = (void*)malloc(size);
return ret;
}
static void operator delete(void *p){
free(p);
cout << "Class::operator delete(void*)" << endl;
}
};
void main(){
StringClass *p = new StringClass;
delete p;
}
結果如下。在new表達式中,先調用了operator new,再調用constructor,最後将指針傳回給p。最後在釋放的時候,先調用了destructor,再調用了operator delete。

既然使用到了delete,懷着“授人以魚不如授人以漁”的想法,推薦讀者了解一下crtdbg這個頭檔案,谷歌一下就知道了。
placement new:定位new
- 在堆裡面申請記憶體,需要尋找空的記憶體,多次申請釋放還會産生大量的記憶體碎片,但這個神器會幫你解決這些問題。使用它,你隻需要申請一次記憶體,就能夠多次建立不同的執行個體,具體參見下例的講解。
- 前面提到,new operator會有三個步驟,第一是申請記憶體,第二是調用構造函數,第三是傳回指針。定位new,之是以為定位,是因為我們事先配置設定好了記憶體,并且指定了必須使用這塊記憶體進行初始化。共有四種文法如下。
new (place_address) type;
new (place_address) type (initializers);
new (place_address) type[size];
new (place_address) type[size]{braced initializers list};
對第一種我們舉個栗子。
#pragma once
#include<iostream>
using namespace std;
class StringClass
{
public:
StringClass(){
cout << "string class construtor" << endl;
}
~StringClass(){
cout << "string class destrutor" << endl;
}
//比起前面的代碼,增添了這個函數
static void *operator new(size_t size, void *p) {
cout << "Class::operator new(size_t,void*)" << endl;
return p;
}
};
void main()
{
StringClass *buf = (StringClass*)malloc(sizeof(StringClass));
memset(buf, 0, sizeof(StringClass));
StringClass *p2 = new(buf) StringClass();
p2->~StringClass();
free(buf);
}
-
上面總共有五個步驟:
-- 上述代碼先使用申請了一塊記憶體,大小使用者根據自己需要去決定。
-- 初始化這塊記憶體。
-- 然後在上面調用了StringClass的構造函數,再令p2指向他。
-- 然後p2使用完畢在上面調用自己的析構函數。
-- 程式結束前将這塊記憶體釋放。假如有需要建立另外一個對象,我們可以不用申請新的記憶體,繼續使用這塊記憶體。
- 與operator new不同的是,使用placement new是無需定義operator delete的,如上,整個過程壓根就沒有用到它。相反,假如你在用戶端使用了它,還會導緻調用析構函數的多次調用。
警告
除了placement new外,其他版本的new與delete都要成對地重載。因為你重載了編譯器的new,它不知道delete需要為你做些什麼。
總結
- new總共分成3類,new operator是一個操作的集合,不是具體的運算符。可以重載的叫做operator new。
- 使用了placement new,必須定義void *operator new(siez_t,void *p),不用定義operator delete。而除了placement new,若定義了operator new/delete需要成對定義。
- 所有的operator new都是隐式靜态的,為了讓代碼易于閱讀,你應該加上static
- 所有的operator new都應該傳回void*
最後的疑問
在很多書籍以及部落格中,都指出了下面這個operator new不能被重載。在C++Primer第五版,白色封面的那本,P727最下方中,明确指出了它不能重載,但實際上,使用placement new的時候我們卻不得不重載這個版本。關于這一點,希望有高人能夠給出意見。