目錄
前言
1.為什么C++要引入new/delete?
2.operator new與operator delete函數
它們的實際作用
Placement New(定位new表達式)
總結
前言
在寫上一篇博客“vector的模擬實現”時,我一直很好奇vector的private成員為什么要用三個封裝后的迭代器指針,來替代原本數據結構順序表中的成員變量,因為原本的數據結構成員是完全滿足需求的,即:
(從數據結構的本質來講,vector完全可以被理解為 C++ 版的、功能更強大的、高度自動化的順序表。)
在學習了實際庫中的vector代碼后,我發現了未見過的東西:"::operator new與::operator delete"。在知道了它們的作用與使用場所后,或許我找到了用迭代器指針替代原本數據成員的原因。
1.為什么C++要引入new/delete?
C語言內存管理方式在C++中是可以繼續使用,比如malloc、realloc等函數。但有些地方這些傳統的空間申請函數就顯得有些無能為力,而且使用起來比較麻煩。
是的,在C++的自定類型數據中,常需要在定義時順便調用構造函數初始化。而原C語言的空間申請函數是沒有這種功能,需要額外操作,于是C++提出了自己的內存管理方式:通過new和delete操作符進行動態內存管理。
簡而言之,什么C++要引入new/delete的原因在于:
new/delete 和 malloc/free最大區別是 new/delete對于【自定義類型】除了開空間時,還會調用構造函數和析構函數。
2.operator new與operator delete函數
首先介紹它們是什么:
它們是C++中的內存分配原語函數。它們只負責分配和釋放原始內存,不涉及對象的構造和析構。
它們的用法:
#include <new> // 包含 operator new 和 operator delete 的聲明// 分配內存
void* memory = ::operator new(size_t bytes);// 釋放內存
::operator delete(void* ptr);
示例
#include <iostream>
#include <new>int main()
{// 分配10個int大小的內存void* int_memory = ::operator new(10 * sizeof(int));std::cout << "內存分配成功,地址: " << int_memory << std::endl;// 使用內存...// 釋放內存::operator delete(int_memory );std::cout << "內存已釋放" << std::endl;return 0;
}
它們與new/delete的關系:
new和delete是用戶進行動態內存申請和釋放的操作符,operator new 和operator delete是系統提供的全局函數,new在底層調用operator new全局函數來申請空間,delete在底層通過 operator delete全局函數來釋放空間。
操作 | 做的事情 |
---|---|
int* p = new int(42); | 1. 調用?operator new(sizeof(int)) ?分配內存2. 在內存上調用? int ?的構造函數?構造對象(設為42) |
delete p; | 1. 調用?p ?的析構函數?析構對象2. 調用? operator delete(p) ?釋放內存 |
void* mem = ::operator new(sizeof(int)); | 只分配內存,不調用構造函數 |
::operator delete(mem); | 只釋放內存,不調用析構函數 |
簡單說:new
/delete
?=? (operator new
/operator delete
?+ 構造函數/析構函數調用)
注意:和new與delete,new[ ]與delete[ ]相同,operator new只能與operator delete匹配。
它們的實際作用
回到前言部分,為什么vector的private成員為什么要用三個封裝后的迭代器指針,來替代原本數據結構順序表中的成員變量呢?
在查看vecotr的實際實現代碼中,我發現 vector類中涉及空間增刪的函數,實際上都是調用的reserve函數,而在reserve函數中則使用了operator new/delete。
我們來看看reserve函數的模擬實現:
void reserve(size_t n){size_t oldSize = size(), oldCapa = capacity();if (n > oldCapa){size_t newcapa = oldCapa == 0 ? 16 : oldCapa * 2;while (newcapa < n)newcapa *= 2;iterator newVec =(iterator)::operator new(newcapa * sizeof(T));if (_start){for (int i = 0; i < oldSize; ++i)new(newVec + i)T(_start[i]);for (int i = 0; i < oldSize; ++i)_start[i].~T();}::operator delete(_start);_start = newVec;_finish = _start + oldSize;_end = _start + newcapa;}else return;}
討論:在普通的reserve函數中,我們通常用new來申請空間,如下所示:
T* newcapa=new T[n];
可這帶來一個問題:new在申請空間的同時會調用構造函數,可是這些空間我們真的需要全部初始化嗎,換句話說這些空間我們真能全部用完嗎?顯然大部分場景是用不完的,因為vector的空間一般呈*2倍速度增長。那么這些不用的空間通過調用構造函數初始化,這不僅造成了一定的性能浪費,還讓后續無法自定義使用這片內存,造成內存資源浪費。
于是,通過用operator new/delete替換之前的new/delete,既解決了性能損失,又滿足了C++內存分配與對象構造分離的目的。
或許有讀者注意到上述代碼中的如下這段代碼,這段代碼實則揭開了為什么vector要使用三個指針作為成員變量的原因:
for (int i = 0; i < oldSize; ++i)new(newVec + i)T(_start[i]);
已知C++程序的一個核心設計思想:將內存分配(Allocation)和對象構造(Construction)分離。通過使用operator new/delete確實做到了只分配空間,不調用構造函數的目的。那么什么時候調用構造函數呢?
——在reserve函數中,通過提前記錄的oldSize精確控制調用構造函數的次數。而實際調用構造函數是通過Placement New(定位new表達式)實現的。
Placement New(定位new表達式)
它是什么?
Placement new 是一種特殊的 new 表達式,它在已分配的內存上構造對象。它不分配內存,只調用構造函數。
語法
new (address) Type(constructor_arguments);
address:一般傳入指針;
Type:某數據類型,可以是內置類型,也可以是自定義類型;constructor_arguments,該類型的構造函數。
使用示例
#include <iostream>
#include <new>class MyClass {
public:int value;MyClass(int v) : value(v) {std::cout << "構造函數被調用,value = " << value << std::endl;}~MyClass() {std::cout << "析構函數被調用,value = " << value << std::endl;}
};int main() {// 1. 只分配內存,不構造對象void* memory = ::operator new(sizeof(MyClass));// 2. 在已分配的內存上構造對象MyClass* obj = new (memory) MyClass(42);std::cout << "對象值: " << obj->value << std::endl;// 3. 顯式調用析構函數obj->~MyClass();// 4. 釋放內存::operator delete(memory);return 0;
}
operator new
?設計時就考慮了與 placement new 的配合使用。這種組合提供了對對象構造和內存分配的完全控制。
現在回到上述有關reserve函數的討論。
現在已知vector中有關的空間操作,全是由reserve函數完成的,其中reserve函數通過使用operator new/delete、定位new表達式完成了“內存分配和對象構造的分離”。
結合定位new表達式的使用語法,有關“vector的private成員為什么要用三個封裝后的迭代器指針,來替代原本數據結構順序表中的成員變量”的答案也就呼之欲出了,或許原因之一就是為了滿足位new表達式的使用語法從而達到“內存分配和對象構造的分離”的目的。
同樣是申請空間,為什么不使用原C語言malloc等函數,而設計出operator new函數呢?
可能的原因或許有很多,但作者認為或許與它們在面對異常時的反應不同:
operator new
?的異常行為
-
當?
operator new
?無法分配內存時,它會拋出?std::bad_alloc
?異常,提醒程序員。 -
這與 C++ 的異常處理機制完美集成。
malloc
?的錯誤處理
-
當?
malloc
?無法分配內存時,它返回?NULL
(或 C++11 中的?nullptr
),需要程序員自己檢查。 -
這要求你檢查返回值,使用 C 風格的錯誤處理。
總結
本文從對“vector的private成員為什么要用三個封裝后的迭代器指針,來替代原本數據結構順序表中的成員變量”疑問中,引出operator new/delete的介紹,以及之后定位表達式new的使用語法。
本文或許對vector為什么要用三個指針,替換原本的使用一個指針加兩個size_t(data, size, capacity)的回答不盡完美,甚至漏洞百出,但好在因此學到了新東西。
感謝你的閱讀。