C++的new&delete
new的程序
new的程序:先分配memory,再呼叫ctor
我們常用的創建物件的方法有兩種
Complex c(1,2); //堆疊
Complex *pc = new Complex(1,2); //堆
第一種創建出來的物件將保存在堆疊上,第二種則在堆上,必須手動回收記憶體空間(通過delete)
為了解釋new的程序,我們先建立一個Complex類
class Complex
{
public:
Complex(...) {...}//建構式
...
private:
double real;
double imag;
};
當我們使用new構建Complex類物件的時候
Complex *pc = new Complex(1,2);
當我們使用new這一個動作,在堆上動態創建一個物件時,編譯器實際上幫你做了三件事:
Complex *pc;
//1.分配記憶體(呼叫 operator new() 函式)
void* memory = operator new(sizeof(Complex));
//2.轉型
pc = static_cast<Complex*>(memory);
//3.呼叫建構式
pc->Complex::Complex(1,2);
- 分配記憶體:
operator new也是一個函式,其內部呼叫malloc(n),拿到sizeof(Complex)大小的記憶體空間;這時候我們得到指向記憶體空間始址的指標memory,它是一個指向viod型別的指標 - 轉型:用
static_cast函式,把步驟①得到的指標memory(這是一個pointer to void)轉換為pointer to Complex,并將其賦值到pc(步驟①和②可以寫在一起) - 呼叫建構式:步驟②得到的指標pc指向的記憶體空間,即為新物件的起始記憶體地址;于是編譯器將通過指標pc呼叫物件的建構式
所以從結果上看,這兩段代碼是等效的
//代碼1.
Complex *pc = new Complex(1,2);
//代碼2.
Complex *pc;
void* memory = operator new(sizeof(Complex));
pc = static_cast<Complex*>(memory);
pc->Complex::Complex(1,2);
malloc和new的區別在于,當malloc失敗時,它不會呼叫分配記憶體失敗處理程式new_handler,因此我們還是要盡可能的使用new,除非有一些特殊的需求
delete的程序
delete的程序:先呼叫dtor,再釋放memory
我們再建立一個包含指標的類String:
class String {
public:
...
~String()
{delete[] m_data;}
...
private:
char* m_data;
};
當我們試用new&delete時:
String* ps = new String("HELLO");
...
delete ps;
編譯器在delete這里實際上幫你做了兩件事:
String::~String(ps); //1.呼叫解構式
operator delete(ps); //2.釋放記憶體
- 呼叫解構式:由于String類是包含指標的,所以設計時不能使用默認解構式,而是多載一個符合需求的解構式,在我們delete ps時,編譯器第一步就是呼叫我們多載后的解構式(沒有多載則呼叫默認)
- 釋放記憶體:
operator delete和operator new一樣也是一個函式,其內部呼叫free(ps)
new的三種形態
有的朋友可能被上面的new和operator new搞暈了,實際上在C++中提到new,至少可能代表以下三種含義:new operator,operator new,placement new
new operator
new operator 就是 new 運算子,不能被多載
我們上面所說的new,都是指new operator,也就是我們平時使用的new
operator new
operator new 是函式,可以被多載,new operator 呼叫它用來分配記憶體,通過多載它,可以改變 new operator 的功能
默認有三種
void* operator new (std::size_t size) throw (std::bad_alloc);
void* operator new (std::size_t size, const std::nothrow_t& nothrow_constant) throw();
void* operator new (std::size_t size, void* ptr) throw();
- 第一種分配size個位元組的存盤空間,并將物件型別進行記憶體對齊,如果成功,回傳一個非空的指標指向首地址,失敗拋出bad_alloc例外, (
A* a = new A;呼叫第一種 - 第二種在分配失敗時不拋出例外,它回傳一個NULL指標, (
A* a = new(std::nothrow) A;//呼叫第二種 - 第三種是 placement new 版本,它本質上是對 operator new 的多載,定義于#include
中,它不分配記憶體,呼叫合適的建構式在 ptr 所指的地方構造一個物件,之后回傳實參指標ptr,下文細談
多載 operator new
class Complex
{
public:
Complex(...) {...}//建構式
...
//多載第一種
void* operator new(size_t size){
cout << "operator new called\n" << endl;
//通過::operator new呼叫了原有的全域的new
return ::operator new(size);
}
void operator delete(void* pointer)
{
cout << "operator delete" << endl;
::operator delete(pointer);
}
private:
double real;
double imag;
};
然后你可以直接呼叫 Complex::operator new(),或者使用 new 來呼叫
int main()
{
Complex* pc2 = new Complex(1,2);
}
輸出
operator new called
operator delete
這里通過::operator new呼叫了原有的全域的new,也就相當于是在分配記憶體之前輸出一句話
如上代碼所示,delete 也有 delete operator 和 operator delete 之分,后者也是可以多載的,并且,如果多載了 operator new,就應該也相應的多載 operator delete,這是良好的編程習慣
多載 operator new 需要注意以下幾點:
- 多載時,回傳型別必須宣告為void*
- 多載時,第一個引數型別必須為表達要求分配空間的大小(位元組),型別為 size_t
- 多載時,可以帶其它引數
帶其他引數的多載:
void* operator new(size_t size, string str) {
cout << "operator new called\n" << endl;
cout << "with string:" << str << endl;
//通過::operator new呼叫了原有的全域的new
return ::operator new(size);
}
呼叫時就可以這樣操作
Complex* pc = new("Test") Complex(1,2);
placement new
placement new 是 c++ 實作的 operator new 版本,它的實作如下
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
// Default placement versions of operator delete.
inline void operator delete (void*, void*) _GLIBCXX_USE_NOEXCEPT { }
inline void operator delete[](void*, void*) _GLIBCXX_USE_NOEXCEPT { }
//@}
可以看到實際上它就回傳了傳進來的地址,根據operator的第二個例子,通過多載全域的operator new之后,new函式的操作就被改變了,也就能猜出,在呼叫new的時候引數需要加上一個地址,placement new的功能就是在這個地址之上進行構造,
placement new 的使用步驟如下:
//1.分配記憶體
char* buff = new char[ sizeof(Complex) * N ];
memset( buff, 0, sizeof(Foo)*N );
//2.構建物件
Complex* pc = new (buff)Complex;
//3.使用物件
pc->XXXXXX();
//4.析構物件,顯式的呼叫類的解構式
pc->~Complex();
//5.銷毀記憶體
delete[] buff;
上面5個步驟是標準的placement new的使用方法
placement new 是用來實作定位構造的,因此可以實作 new operator 三步操作中的呼叫建構式這一步(在取得了足夠記憶體空間后,在這塊記憶體空間是上構造一個物件)
上面寫的pc->Complex::Complex(1,2);這句話并不是一個標準的寫法,正確的寫法是使用 placement new:
#include <new.h>
int main()
{
char* buff = new char[ sizeof(Complex) ];
Complex* pc = new(buff) Complex(1,2);
...
}
placement new 它實作了在指定記憶體地址上用指定型別的建構式來構造一個物件的功能,這塊指定的地址既可以是堆疊,又可以是堆,placement 對此不加區分
除非特別必要,不要直接使用placement new ,這畢竟不是用來構造物件的正式寫法,只不過是new operator的一個步驟而已,使用new operator地編譯器會自動生成對placement new的呼叫的代碼,因此也會相應的生成使用delete時呼叫解構式的代碼
如果是像上面那樣在堆疊上使用了placement new,則必須手工呼叫解構式,這也是顯式呼叫解構式的唯一情況
pc->~Complex();
當我們覺得默認的new operator對記憶體的管理不能滿足我們的需要,而希望自己手工的管理記憶體時,placement new就有用了,STL中的allocator就使用了這種方式,借助placement new來實作更靈活有效的記憶體管理,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/64979.html
標籤:C++
上一篇:stl_list復習
