網站首頁 編程語言 正文
前言
有時候我們覺得,C++術語仿佛是要故意讓人難以理解似的。這里就有一個例子:請說明new operator 和 operator new 之間的差異。
上面這段話出自《More Effective C++》中的條款8。有興趣的讀者可以閱讀這本書。現在就讓我們揭開這神秘的面紗吧。
new 到底做了什么
new
是C++的一個關鍵字、操作符。
當我們執行Test* pt = new Test();
這句代碼時,實際上干了三件事情:
- 分配內存
- 調用Constructor函數
- 返回分配好的指針
為什么這么說呢?口說無憑眼見為實,請接著往下看。
通過VS2022查看匯編代碼進行驗證
首先我們需要寫一個空類,然后在main中new出這個類。代碼可參考如下:
class A { public: A() { } ~A() { } }; int main() { A* p = new A(); delete p; p = nullptr; return 0; }
第一步:在創建這一行添加斷點(可左擊該行行首或者在該行按F9即可)。
第二步:開始調試到當前斷點處(可按F5)。
第三步:在上方功能欄中點擊【Debug】->【Windows】->【Disassembly】。中文對應的是【調試】->【窗口】->【反匯編】。詳細請看下圖。
操作完上面三步之后我們就到了匯編代碼。由于重點不是研究匯編語言,所以這里我就僅對上面那三步進行標記。驗證一下上面的一個猜想。
那么這里我們用到的new操作符,也就是new operator
,在《C++ Primer》書中也被稱為new expression
。
operator new
功能:只負責內存分配
operator new默認情況下調用分配內存的代碼,去嘗試在堆區獲取一段空間,如果成功就返回,如果失敗,則調用new_hander
。有關new_hander我之前寫了一篇:new_hander文章鏈接
重載類內operator new
下面對operator new重載,進行測試;
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); // 通過::operator new調用了全局的new } }; int main() { A* pt = new A(); delete pt; pt = nullptr; return 0; }
運行結果:
可以看到先打印類內的operator new再調用constructor函數最后調用destructor函數。
重載全局 ::operator new
若要重載全局的::operator new
時,最后就不能return
自身了需要寫成malloc(size)
。對應的delete
也有delete operator
和 operator delete
倆種,operator delete
也是可以重載的。所以一般來說重載了operator new
就需要重載對應的operator delete
了。
具體請看下面的代碼:
新增一個全局的operator new函數
void* operator new(size_t size) { std::cout << "Call global operator new" << "\t" << size << std::endl; return malloc(size); }
運行結果:
直接調用operator new
該函數我們可以進行重載,但是第一參數的類型必須是size_t。而且我們還可以單獨調用operator new
。將返回一個void類型的指針。
在原有代碼基礎上,增加一個成員函數用于輸出日志。
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); // 通過::operator new調用了全局的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; }
運行結果:
可以看到只打印了全局的operator new函數已經析構函數。
Placement new
頭文件:#include <new> 或者#include <new.h>
可以直接調用constructor函數,是operator new的一個特殊版本,也被稱為placement new
函數。
需要實現一個void* operator new(size_t, void* location)
的重載版本。不需要申請內存只需要返回當前對象即可。
調用的語法:new(ObjectName) ClassName(構造函數的參數)
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); // 通過::operator new調用了全局的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); // 創建內存 new(pa) A(); // 調用構造函數 pa->print(); delete pa; pa = nullptr; return 0; }
運行結果:
這里的operator new的目的是要為對象找內存,然后返回一個指針指向它。在placement new的情況下,調用者已經知道指向內存的指針了,所以placement new唯一需要做的就是將已獲得指針進行返回。雖然說size_t參數沒有用到但是必須要加,之所以不給形參名是因為防止編譯器抱怨“某某變量未被使用”。
刪除與內存釋放
為了避免內存泄漏,每一個動態分配都必須匹配一個釋放動作。
內存釋放的動作是由operator delete
執行,函數原型:void operator delete(void* object);
當我們寫了這句代碼時delete pa;
實際上執行了倆件事。
1、調用destructor函數
2、釋放對象所占的內存資源
轉換成代碼就相當于:
pa->~A(); operator delete(pa);
使用operator new創建對象該如何釋放
當我們在創建對象時,沒有調用constructor
函數,那么釋放內存時也不需要調用destructor
函數。只需要operator delete(pa);
。
int main() { void* rawMemory = operator new(sizeof(A)); . ...其他代碼 operator delete(rawMemory); return 0; }
上面這段代碼其實就等價于C語言里面調用malloc和free函數。
使用placement new創建對象時該如何釋放
如果使用placement new
在內存中產生對象,我們不能使用delete operator
,因為會調用operator delete
函數來釋放內存。首先該內存并不是由該對象的operator new
函數分配而來。它僅僅做了一個返回而已,所以這種情況下只需要調用destructor
函數即可。
int main() { void* rawMemory = operator new(sizeof(A)); A* pa = static_cast<A*>(rawMemory); // 創建內存 new(pa)A(); // 調用構造函數 pa->~A(); pa = nullptr; operator delete(rawMemory); return 0; }
在上面這段代碼中,pa對象就是使用placement new
,所以最后只需要調用destructor
函數。
針對數組的創建和釋放
當我們使用A* pa = new A[10];
這段代碼時,分配內存的方式將會發生變化。
1、由operator new
改為 operator new[]
,也被叫為array new
。同樣array new也可以被重載,
2、array new
必須調用數組中的每個對象的constructor
函數。上面那個例子就會調用10個A的無參構造函數。
3、array new
在釋放內存時。上面那個例子就會調用10個A的destructor
函數。
4、該類必須有無參構造函數。
所以我們同樣也可以修改operator new[]所調用的 new operator函數,以及delete[] operator。
系統維護開銷
在面對數組時,new 會額外分配空間來存儲new的長度(一般為一個指針大小,32位平臺下4字節,64位平臺下8字節)。這個叫系統維護開銷。
下面是測試代碼,類A是個空類只占一個字節,正常來說應該申請10個字節的內存。
int main() { A* pa = new A[10]; delete[] pa; return 0; }
32位環境下:
64位環境下:
可以看到對申請了一個指針的內存用來存放申請對象的個數。
總結
下面針對new的三種使用方式做了一個使用場景總結,切記操作對應的new 時還需要對應的delete。
1、需要將對象創建在堆區,那么就使用 new operator
也就是new
操作符。它會幫你分配內存并調用constructor
函數。
2、僅需要分配內存,那么就使用operator new
,這樣就不會調用constructor
函數。
3、需要在堆區創建對象時自定義內存分配方式,那么就需要重寫operator new
函數然后使用new operator
即可。
4、需要在已分配的內存中調用構造函數,那么就使用placement new
。
原文鏈接:https://blog.csdn.net/qq_45254369/article/details/126276038
相關推薦
- 2022-07-26 對Python中GIL(全局解釋器鎖)的一點理解淺析_python
- 2023-02-06 C#基于時間輪調度實現延遲任務詳解_C#教程
- 2023-08-28 react antd常見報錯Each child in a list should have a u
- 2022-05-19 Pytorch使用技巧之Dataloader中的collate_fn參數詳析_python
- 2023-02-18 Flow轉LiveData數據丟失原理詳解_Android
- 2022-05-24 C語言的strcpy函數你了解嗎_C 語言
- 2023-05-13 向Rust學習Go考慮簡單字符串插值特性示例解析_Golang
- 2022-12-11 python中windows鏈接linux執行命令并獲取執行狀態的問題小結_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支