C++的new&delete

new的過程

new的過程:先分配memory,再調用ctorc++

咱們經常使用的建立對象的方法有兩種編程

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這一個動做,在上動態建立一個對象時,編譯器實際上幫你作了三件事:code

Complex *pc;

//1.分配內存
void* memory = operator new(sizeof(Complex));   
//2.轉型
pc = static_cast<Complex*>(memory);         
//3.調用構造函數
pc->Complex::Complex(1,2);
  1. 分配內存:operator new也是一個函數,其內部調用malloc(n),拿到sizeof(Complex)大小的內存空間;這時候咱們獲得指向內存空間始址的指針memory,它是一個指向viod類型的指針
  2. 轉型:用static_cast函數,把步驟①獲得的指針memory(這是一個pointer to void)轉換爲pointer to Complex,並將其賦值到pc(步驟①和②能夠寫在一塊兒)
  3. 調用構造函數:步驟②獲得的指針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.釋放內存
  1. 調用析構函數:因爲String類是包含指針的,因此設計時不能使用默認析構函數,而是重載一個符合需求的析構函數,在咱們delete ps時,編譯器第一步就是調用咱們重載後的析構函數(沒有重載則調用默認)
  2. 釋放內存:operator deleteoperator new同樣也是一個函數,其內部調用free(ps)

new的三種形態

有的朋友可能被上面的newoperator new搞暈了,實際上在C++中提到new,至少可能表明如下三種含義:new operatoroperator newplacement new

new operator

咱們上面所說的new,都是指new operator,也就是咱們平時使用的new

operator new

new operator的第一步分配內存是經過調用operator new來完成的,這裏的「new」其實是像加減乘除同樣的操做符,所以也是能夠重載的

operator new默認狀況下首先調用分配內存的代碼,嘗試獲得一段堆上的空間,若是成功就返回,若是失敗,則轉而去調用一個new_hander,而後繼續重複前面過程

若是咱們對這個過程不滿意,就能夠重載operator new,來設置咱們但願的行爲,例如在Complex類里加入:

class Complex
{
public:
    Complex(...) {...}//構造函數
    ...
    void* operator new(size_t size){
        printf("operator new called\n");
        //經過::operator new調用了原有的全局的new
        return ::operator new(size);
    }
private:
    double real;
    double imag;
};

這裏經過::operator new調用了原有的全局的new,在分配內存以前輸出一句話

delete也有delete operatoroperator delete之分,後者也是能夠重載的。而且,若是重載了operator new,就應該也相應的重載operator delete,這是良好的編程習慣。

placement new

placement new是用來實現定位構造的,所以能夠實現new operator三步操做中的調用構造函數這一步(在取得了足夠內存空間後,在這塊內存空間是上構造一個對象)

上面寫的pc->Complex::Complex(1,2);這句話並非一個標準的寫法,正確的寫法是使用placement new

#include <new.h>

int main()
{
    char memory[sizeof(Complex)];
    Complex* pc = (Complex*)memory;
    new(pc) Complex(1, 2);
}

new(pc) Complex(1, 2);這種奇怪的寫法即是placement new了,它實現了在指定內存地址上用指定類型的構造函數來構造一個對象的功能,後面Complex(1, 2)就是對構造函數的顯式調用

這裏不難發現,這塊指定的地址既能夠是棧,又能夠是堆,placement對此不加區分
除非特別必要,不要直接使用placement new ,這畢竟不是用來構造對象的正式寫法,只不過是new operator的一個步驟而已。使用new operator地編譯器會自動生成對placement new的調用的代碼,所以也會相應的生成使用delete時調用析構函數的代碼

若是是像上面那樣在棧上使用了placement new,則必須手工調用析構函數,這也是顯式調用析構函數的惟一狀況

pc->~Complex();

當咱們以爲默認的new operator對內存的管理不能知足咱們的須要,而但願本身手工的管理內存時,placement new就有用了。STL中的allocator就使用了這種方式,藉助placement new來實現更靈活有效的內存管理。

相關文章
相關標籤/搜索