淺談C++中各種不同意義的new和delete的使用
前言
有時(shí)候我們覺(jué)得,C++術(shù)語(yǔ)仿佛是要故意讓人難以理解似的。這里就有一個(gè)例子:請(qǐng)說(shuō)明new operator 和 operator new 之間的差異。
上面這段話(huà)出自《More Effective C++》中的條款8。有興趣的讀者可以閱讀這本書(shū)?,F(xiàn)在就讓我們揭開(kāi)這神秘的面紗吧。
new 到底做了什么
new
是C++的一個(gè)關(guān)鍵字、操作符。
當(dāng)我們執(zhí)行Test* pt = new Test();
這句代碼時(shí),實(shí)際上干了三件事情:
- 分配內(nèi)存
- 調(diào)用Constructor函數(shù)
- 返回分配好的指針
為什么這么說(shuō)呢?口說(shuō)無(wú)憑眼見(jiàn)為實(shí),請(qǐng)接著往下看。
通過(guò)VS2022查看匯編代碼進(jìn)行驗(yàn)證
首先我們需要寫(xiě)一個(gè)空類(lèi),然后在main中new出這個(gè)類(lèi)。代碼可參考如下:
class A { public: A() { } ~A() { } }; int main() { A* p = new A(); delete p; p = nullptr; return 0; }
第一步:在創(chuàng)建這一行添加斷點(diǎn)(可左擊該行行首或者在該行按F9即可)。
第二步:開(kāi)始調(diào)試到當(dāng)前斷點(diǎn)處(可按F5)。
第三步:在上方功能欄中點(diǎn)擊【Debug】->【W(wǎng)indows】->【Disassembly】。中文對(duì)應(yīng)的是【調(diào)試】->【窗口】->【反匯編】。詳細(xì)請(qǐng)看下圖。
操作完上面三步之后我們就到了匯編代碼。由于重點(diǎn)不是研究匯編語(yǔ)言,所以這里我就僅對(duì)上面那三步進(jìn)行標(biāo)記。驗(yàn)證一下上面的一個(gè)猜想。
那么這里我們用到的new操作符,也就是new operator
,在《C++ Primer》書(shū)中也被稱(chēng)為new expression
。
operator new
功能:只負(fù)責(zé)內(nèi)存分配
operator new默認(rèn)情況下調(diào)用分配內(nèi)存的代碼,去嘗試在堆區(qū)獲取一段空間,如果成功就返回,如果失敗,則調(diào)用new_hander
。有關(guān)new_hander我之前寫(xiě)了一篇:new_hander文章鏈接
重載類(lèi)內(nèi)operator new
下面對(duì)operator new重載,進(jìn)行測(cè)試;
class A { public: A() { std::cout << "Call A Constructor!" << std::endl; } ~A() { std::cout << "Call A Destructor!" << std::endl; } void* operator new(size_t size) { std::cout << "Call operator new" << "\t size = " << size << std::endl; return ::operator new(size); // 通過(guò)::operator new調(diào)用了全局的new } }; int main() { A* pt = new A(); delete pt; pt = nullptr; return 0; }
運(yùn)行結(jié)果:
可以看到先打印類(lèi)內(nèi)的operator new再調(diào)用constructor函數(shù)最后調(diào)用destructor函數(shù)。
重載全局 ::operator new
若要重載全局的::operator new
時(shí),最后就不能return
自身了需要寫(xiě)成malloc(size)
。對(duì)應(yīng)的delete
也有delete operator
和 operator delete
倆種,operator delete
也是可以重載的。所以一般來(lái)說(shuō)重載了operator new
就需要重載對(duì)應(yīng)的operator delete
了。
具體請(qǐng)看下面的代碼:
新增一個(gè)全局的operator new函數(shù)
void* operator new(size_t size) { std::cout << "Call global operator new" << "\t" << size << std::endl; return malloc(size); }
運(yùn)行結(jié)果:
直接調(diào)用operator new
該函數(shù)我們可以進(jìn)行重載,但是第一參數(shù)的類(lèi)型必須是size_t。而且我們還可以單獨(dú)調(diào)用operator new
。將返回一個(gè)void類(lèi)型的指針。
在原有代碼基礎(chǔ)上,增加一個(gè)成員函數(shù)用于輸出日志。
class A { public: A() { std::cout << "Call A Constructor!" << std::endl; } ~A() { std::cout << "Call A Destructor!" << std::endl; } void* operator new(size_t size) { std::cout << "Call operator new" << "\t size = " << size << std::endl; return ::operator new(size); // 通過(guò)::operator new調(diào)用了全局的new } void print() { std::cout << "ha ha !" << std::endl; } }; void* operator new(size_t size) { std::cout << "Call global operator new" << "\t size = " << size << std::endl; return malloc(size); } int main() { void* rawMemory = operator new(sizeof(A)); A* pa = static_cast<A*>(rawMemory); pa->print(); delete pa; pa = nullptr; return 0; }
運(yùn)行結(jié)果:
可以看到只打印了全局的operator new函數(shù)已經(jīng)析構(gòu)函數(shù)。
Placement new
頭文件:#include <new> 或者#include <new.h>
可以直接調(diào)用constructor函數(shù),是operator new的一個(gè)特殊版本,也被稱(chēng)為placement new
函數(shù)。
需要實(shí)現(xiàn)一個(gè)void* operator new(size_t, void* location)
的重載版本。不需要申請(qǐng)內(nèi)存只需要返回當(dāng)前對(duì)象即可。
調(diào)用的語(yǔ)法:new(ObjectName) ClassName(構(gòu)造函數(shù)的參數(shù))
class A { public: A() { std::cout << "Call A Constructor!" << std::endl; } ~A() { std::cout << "Call A Destructor!" << std::endl; } void* operator new(size_t size) { std::cout << "Call operator new" << "\t size = " << size << std::endl; return ::operator new(size); // 通過(guò)::operator new調(diào)用了全局的new } void* operator new(size_t size, void* location) { std::cout << "Call operator new(size_t size, void* location)" << std::endl; return location; } void print() { std::cout << "ha ha !" << std::endl; } }; int main() { void* rawMemory = operator new(sizeof(A)); A* pa = static_cast<A*>(rawMemory); // 創(chuàng)建內(nèi)存 new(pa) A(); // 調(diào)用構(gòu)造函數(shù) pa->print(); delete pa; pa = nullptr; return 0; }
運(yùn)行結(jié)果:
這里的operator new的目的是要為對(duì)象找內(nèi)存,然后返回一個(gè)指針指向它。在placement new的情況下,調(diào)用者已經(jīng)知道指向內(nèi)存的指針了,所以placement new唯一需要做的就是將已獲得指針進(jìn)行返回。雖然說(shuō)size_t參數(shù)沒(méi)有用到但是必須要加,之所以不給形參名是因?yàn)榉乐咕幾g器抱怨“某某變量未被使用”。
刪除與內(nèi)存釋放
為了避免內(nèi)存泄漏,每一個(gè)動(dòng)態(tài)分配都必須匹配一個(gè)釋放動(dòng)作。
內(nèi)存釋放的動(dòng)作是由operator delete
執(zhí)行,函數(shù)原型:void operator delete(void* object);
當(dāng)我們寫(xiě)了這句代碼時(shí)delete pa;
實(shí)際上執(zhí)行了倆件事。
1、調(diào)用destructor函數(shù)
2、釋放對(duì)象所占的內(nèi)存資源
轉(zhuǎn)換成代碼就相當(dāng)于:
pa->~A(); operator delete(pa);
使用operator new創(chuàng)建對(duì)象該如何釋放
當(dāng)我們?cè)趧?chuàng)建對(duì)象時(shí),沒(méi)有調(diào)用constructor
函數(shù),那么釋放內(nèi)存時(shí)也不需要調(diào)用destructor
函數(shù)。只需要operator delete(pa);
。
int main() { void* rawMemory = operator new(sizeof(A)); . ...其他代碼 operator delete(rawMemory); return 0; }
上面這段代碼其實(shí)就等價(jià)于C語(yǔ)言里面調(diào)用malloc和free函數(shù)。
使用placement new創(chuàng)建對(duì)象時(shí)該如何釋放
如果使用placement new
在內(nèi)存中產(chǎn)生對(duì)象,我們不能使用delete operator
,因?yàn)闀?huì)調(diào)用operator delete
函數(shù)來(lái)釋放內(nèi)存。首先該內(nèi)存并不是由該對(duì)象的operator new
函數(shù)分配而來(lái)。它僅僅做了一個(gè)返回而已,所以這種情況下只需要調(diào)用destructor
函數(shù)即可。
int main() { void* rawMemory = operator new(sizeof(A)); A* pa = static_cast<A*>(rawMemory); // 創(chuàng)建內(nèi)存 new(pa)A(); // 調(diào)用構(gòu)造函數(shù) pa->~A(); pa = nullptr; operator delete(rawMemory); return 0; }
在上面這段代碼中,pa對(duì)象就是使用placement new
,所以最后只需要調(diào)用destructor
函數(shù)。
針對(duì)數(shù)組的創(chuàng)建和釋放
當(dāng)我們使用A* pa = new A[10];
這段代碼時(shí),分配內(nèi)存的方式將會(huì)發(fā)生變化。
1、由operator new
改為 operator new[]
,也被叫為array new
。同樣array new也可以被重載,
2、array new
必須調(diào)用數(shù)組中的每個(gè)對(duì)象的constructor
函數(shù)。上面那個(gè)例子就會(huì)調(diào)用10個(gè)A的無(wú)參構(gòu)造函數(shù)。
3、array new
在釋放內(nèi)存時(shí)。上面那個(gè)例子就會(huì)調(diào)用10個(gè)A的destructor
函數(shù)。
4、該類(lèi)必須有無(wú)參構(gòu)造函數(shù)。
所以我們同樣也可以修改operator new[]所調(diào)用的 new operator函數(shù),以及delete[] operator。
系統(tǒng)維護(hù)開(kāi)銷(xiāo)
在面對(duì)數(shù)組時(shí),new 會(huì)額外分配空間來(lái)存儲(chǔ)new的長(zhǎng)度(一般為一個(gè)指針大小,32位平臺(tái)下4字節(jié),64位平臺(tái)下8字節(jié))。這個(gè)叫系統(tǒng)維護(hù)開(kāi)銷(xiāo)。
下面是測(cè)試代碼,類(lèi)A是個(gè)空類(lèi)只占一個(gè)字節(jié),正常來(lái)說(shuō)應(yīng)該申請(qǐng)10個(gè)字節(jié)的內(nèi)存。
int main() { A* pa = new A[10]; delete[] pa; return 0; }
32位環(huán)境下:
64位環(huán)境下:
可以看到對(duì)申請(qǐng)了一個(gè)指針的內(nèi)存用來(lái)存放申請(qǐng)對(duì)象的個(gè)數(shù)。
總結(jié)
下面針對(duì)new的三種使用方式做了一個(gè)使用場(chǎng)景總結(jié),切記操作對(duì)應(yīng)的new 時(shí)還需要對(duì)應(yīng)的delete。
1、需要將對(duì)象創(chuàng)建在堆區(qū),那么就使用 new operator
也就是new
操作符。它會(huì)幫你分配內(nèi)存并調(diào)用constructor
函數(shù)。
2、僅需要分配內(nèi)存,那么就使用operator new
,這樣就不會(huì)調(diào)用constructor
函數(shù)。
3、需要在堆區(qū)創(chuàng)建對(duì)象時(shí)自定義內(nèi)存分配方式,那么就需要重寫(xiě)operator new
函數(shù)然后使用new operator
即可。
4、需要在已分配的內(nèi)存中調(diào)用構(gòu)造函數(shù),那么就使用placement new
。
到此這篇關(guān)于淺談C++中各種不同意義的new和delete的使用的文章就介紹到這了,更多相關(guān)C++ new和delete 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt中簡(jiǎn)單的按鈕槽函數(shù)傳遞參數(shù)方法
這篇文章主要介紹了Qt中簡(jiǎn)單的按鈕槽函數(shù)傳遞參數(shù)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11初識(shí)C++?Vector模板與實(shí)例化原理
這篇文章主要為大家介紹了初識(shí)C++?Vector模板與實(shí)例化原理,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12超詳細(xì)解析C++實(shí)現(xiàn)歸并排序算法
歸并排序是比較穩(wěn)定的排序方法。它的基本思想是把待排序的元素分解成兩個(gè)規(guī)模大致相等的子序列。本文將用C++實(shí)現(xiàn)這一排序算法,需要的可以參考一下2022-09-09vs2019配置Qt5開(kāi)發(fā)環(huán)境(圖文教程)
本文主要介紹了如何使用visual studi2019配置qt5開(kāi)發(fā)環(huán)境,以及創(chuàng)建qt項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12wchar_t,char,string,wstring之間的相互轉(zhuǎn)換
以下是對(duì)wchar_t,char,string,wstring之間的相互轉(zhuǎn)換進(jìn)行了詳細(xì)的分析介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助2013-09-09