天天看點

C++基礎——new與delete

本文目錄

專業名稱

new operator:new操作符,new表達式

operator new:new運算符

placement new:定位new

delete operator:delete操作符,delete表達式

operator delete:delete運算符

delete沒有placement版本 各個版本的解釋 1. new operator

new operator:new表達式

實際上隻是一個稱呼,是三個操作的集合。

  • 申請記憶體(調用operator new)
  • 在記憶體上調用構造函數
  • 将申請的記憶體空間傳回

delete operator:delete表達式

delete operator也是一個稱呼,是兩個操作的集合。

  • 調用析構函數
  • 調用operator delete釋放空間

operator new/delete:new/delete運算符

  • 分為::operator new與Class::operator new。一個是全局的,一個是類内部定義的。平時聽到的new與delete重載,就是重載這裡的new/delete。注意點如下。

    -- class中的new與delete是隐式靜态的。也就是不管你有沒有聲明為static,它都是static的。

    -- 非static成員在沒有執行個體存在的情況下是不能使用的。而當我們定義一個執行個體時,很明顯此時執行個體還沒有存在,這個時候不可能調用一個非static的成員,是以operator new/delete必須是static的。

    -- static成員是不會隐式傳入this指針的,是以在operator new/delete中不能使用任何類的非靜态成員。

    -- 非靜态的成員函數在編譯器會在argument list的第一個位置插入一個本身類型的this指針。是以一個函數名,參數清單與全局函數一模一樣的函數,也不會出現二義性問題。但是一個static成員函數,是不會隐私插入this指針的,是以當我們在class中重載new/delete時,客戶定義執行個體時需要确認是否在全局也定義了new,否則會産生二義性。舉個栗子。

//VS2013
class StringClass
{
public:
    void *operator new(size_t size);
};
void *operator new(size_t size);
void main()
{
    //1. 調用operator new申請記憶體
    //2. 調用構造函數
    //二義性,同時存在兩個可用的operator new
    StringClass *p = new StringClass;
}
           

-- PS:關于函數比對規則,一開始接觸覺得很簡單,當某天觸發了bug的時候就會“書到用時方恨少”,這方面推薦兩個連接配接給大家。看這裡,還有這裡

  • 關于operator new/delete标準庫提供了8個版本,這些版本的new/delete我們都是可以重載的。如下。
//八個版本中有2個new版本有可能抛出異常
void* operator new(size_t t);
void* operator new[](size_t t);
void* operator delete(void*)noexcept;
void* operator delete[](void*)noexcept;

//nothrow_t為空結構體,用于區分上面與下面運算符
void* operator new(size_t t,nothrow_t&)noexcept;
void* operator new(size_t t,nothrow_t&)noexcept;
void* operator delete(void *,nothrow_t&)noexcept;
void* operator delete[](void *,nothrow_t&)noexcept;
           

理論這麼多,下面先來個栗子。

//StringClass.h
//自己定義的一個類
#pragma once
#include<iostream>
using namespace std;
class StringClass
{
public:
    StringClass(){
        cout << "string class construtor" << endl;
    }
    ~StringClass(){
        cout << "string class destrutor" << endl;
    }
    void *operator new(size_t size){
        cout << "Class::operator new(size_t) " << size << endl;
        void *ret = (void*)malloc(size);
        return ret;
    }
    static void operator delete(void *p){
        free(p);
        cout << "Class::operator delete(void*)" << endl;
    }
};
void main(){
    StringClass *p = new StringClass;
    delete p;
}
           

結果如下。在new表達式中,先調用了operator new,再調用constructor,最後将指針傳回給p。最後在釋放的時候,先調用了destructor,再調用了operator delete。

C++基礎——new與delete

既然使用到了delete,懷着“授人以魚不如授人以漁”的想法,推薦讀者了解一下crtdbg這個頭檔案,谷歌一下就知道了。

placement new:定位new

  • 在堆裡面申請記憶體,需要尋找空的記憶體,多次申請釋放還會産生大量的記憶體碎片,但這個神器會幫你解決這些問題。使用它,你隻需要申請一次記憶體,就能夠多次建立不同的執行個體,具體參見下例的講解。
  • 前面提到,new operator會有三個步驟,第一是申請記憶體,第二是調用構造函數,第三是傳回指針。定位new,之是以為定位,是因為我們事先配置設定好了記憶體,并且指定了必須使用這塊記憶體進行初始化。共有四種文法如下。
new (place_address) type;
new (place_address) type (initializers);
new (place_address) type[size];
new (place_address) type[size]{braced initializers list};
           

對第一種我們舉個栗子。

#pragma once
#include<iostream>
using namespace std;

class StringClass
{
public:
    StringClass(){
        cout << "string class construtor" << endl;
    }
    ~StringClass(){
        cout << "string class destrutor" << endl;
    }
    //比起前面的代碼,增添了這個函數
    static void *operator new(size_t size, void *p)    {
        cout << "Class::operator new(size_t,void*)" << endl;
        return p;
    }
};

void main()
{
    StringClass *buf = (StringClass*)malloc(sizeof(StringClass));
    memset(buf, 0, sizeof(StringClass));
    StringClass *p2 = new(buf) StringClass();
    p2->~StringClass();
    free(buf);
}
           
  • 上面總共有五個步驟:

    -- 上述代碼先使用申請了一塊記憶體,大小使用者根據自己需要去決定。

    -- 初始化這塊記憶體。

    -- 然後在上面調用了StringClass的構造函數,再令p2指向他。

    -- 然後p2使用完畢在上面調用自己的析構函數。

    -- 程式結束前将這塊記憶體釋放。假如有需要建立另外一個對象,我們可以不用申請新的記憶體,繼續使用這塊記憶體。

C++基礎——new與delete
  • 與operator new不同的是,使用placement new是無需定義operator delete的,如上,整個過程壓根就沒有用到它。相反,假如你在用戶端使用了它,還會導緻調用析構函數的多次調用。

警告

除了placement new外,其他版本的new與delete都要成對地重載。因為你重載了編譯器的new,它不知道delete需要為你做些什麼。

總結

  • new總共分成3類,new operator是一個操作的集合,不是具體的運算符。可以重載的叫做operator new。
  • 使用了placement new,必須定義void *operator new(siez_t,void *p),不用定義operator delete。而除了placement new,若定義了operator new/delete需要成對定義。
  • 所有的operator new都是隐式靜态的,為了讓代碼易于閱讀,你應該加上static
  • 所有的operator new都應該傳回void*

最後的疑問

在很多書籍以及部落格中,都指出了下面這個operator new不能被重載。在C++Primer第五版,白色封面的那本,P727最下方中,明确指出了它不能重載,但實際上,使用placement new的時候我們卻不得不重載這個版本。關于這一點,希望有高人能夠給出意見。