天天看點

new 與 delete 操作符

new 和 delete 是C++ 中一對動态申請記憶體的操作符。

new_handler 行為

在std的命名空間中,有這樣的代碼:

namespace std
{
    typedef void (*) () new_handler;
    new_handler set_new_handler(new_handler p) throw();
}
           

set_new_handler

的作用是,允許使用者設定目前operator new 失敗時的函數,就是

new_handler

它會傳回之前的

new_handler

很一般的使用如下:

void out_of_memory()
{
    cerr<<"out !"<<endl;
}

int main()
{
    set_new_handler(out_of_memory);
    int* p = new int[];
}
           

有時候,我們希望對于一些class ,當new 記憶體失敗時能夠有它獨立的處理函數,那麼基于

set_new_handler

的一個使用可以如下:

class Some_class
{
    static new_handler current_handler; //目前類的new_handler
    int c[];
public:
    Some_class(){}
    static new_handler set_new_handler(new_handler inn)
    {
        new_handler old(current_handler);
        current_handler = inn;
        return old;
    }
    static void* operator new (size_t size)
    {
        new_handler old = ::set_new_handler(current_handler);
        void* p = ::operator new(size);
        set_new_handler(old); //需要恢複全局的new_handler
        return p;
    }
};

new_handler Some_class::current_handler = nullptr;
           

在以前的博文中,有提到利用類來管理資源。

這裡其實也可以運用一樣的手法,來自動設定和恢複

new_handler

于是,再更改一下,就變成這樣了:

class New_handler_holder
{
    new_handler old_handler;

    //防止copy
    New_handler_holder(const New_handler_holder&);
    New_handler_holder& operator = (const New_handler_holder&);
public:
    New_handler_holder(new_handler inn)
    {
        old_handler = set_new_handler(inn);
    }
    ~New_handler_holder()
    {
        set_new_handler(old_handler);
    }
};

class Some_class
{
    static new_handler current_handler;
    int c[L];
public:
    Some_class(){}
    static new_handler set_new_handler(new_handler inn)
    {
        new_handler old(current_handler);
        current_handler = inn;
        return old;
    }
    static void* operator new (size_t size)
    {
        New_handler_holder h(current_handler); //這裡這樣運用
        return ::operator new(size);
    }
};

new_handler Some_class::current_handler = nullptr;
           

如果每次類需要自己的new_handler 的時候,都要編寫一段幾乎相同的代碼。

嗯~于是,我們可以利用模闆的技術,幫我們完成代碼的複用。

先聲明一個類,用來包含new_handler:

template<typename T>
class New_handler_support
{
    static new_handler current_handler;

public:
    static new_handler set_new_handler(new_handler inn)
    {
        new_handler old(inn);
        current_handler = inn;
        return old;
    }
    static void* operator new (size_t size)
    {
        New_handler_holder h(current_handler);
        return ::operator new(size);
    }
};

template<typename T>
new_handler New_handler_support<T>::current_handler = nullptr;

           

然後使用的時候,讓需要有自己new_handler 的類繼承它就可以了:

//雖然是public 繼承,但不是is-a 關系!!
class Some_class:public New_handler_support<Some_class>
{
    int c[L];
public:
    Some_class(){} 
    //不需要重新編寫operator new 函數
};
           

編寫new 和 delete 時的規則

在global 的作用域中,存在着關于new 和 delete 的函數。

但C++ 提供了操作符重載的機制,是以,我們也可以自己編寫 new 和 delete 函數。

至于為什麼需要自己編寫和什麼時候适合自己編寫 new 和 delete 函數,在[1] 中的條款50 中有說明。

首先,從operator new 開始。

我們知道,在正常的情況下,如果new 一個記憶體失敗的時候,在抛出一個

bad_alloc

異常之前,會循環調用new_handler 的函數,直到記憶體配置設定成功。

它也要能夠處理配置設定記憶體為0 時的情況。C++ 規定,即使客戶要求0 bytes,operator new 也得傳回一個合理的指針[1]。那麼在處理這個情況時,可以傳回指向1 個byte 的指針。

是以,operator new 應該遵守以下的規則[1]:

  • 應該包含一個無窮循環,并在其中嘗試配置設定記憶體,如果它無法滿足記憶體需求,就該調用new-handler.
  • 應該能夠處理0 bytes 的申請。

于是,我們可以這麼寫一段簡單的執行個體代碼:

//這是配置設定記憶體的行為,為了友善,直接使用malloc
//但是實際開發中,這部分可能與自己項目的記憶體管理相關
void* get_memory(size_t size)
{
    if(size < )
        return malloc(size);
    else return nullptr;
}
//自己編寫的new 
void* operator new (size_t size) throw(bad_alloc)
{   
    if(size == )
        size = ;   //處理0 bytes 的處理
    while(true)
    {
        void* p = get_memory(size);
        if(p!=nullptr)
            return p;

        //為了過去全局的handler
        //但是不具備線程安全性
        new_handler global_handler = set_new_handler();
        set_new_handler(global_handler);

        if(global_handler) global_handler();
        else    throw bad_alloc();
    }
};
           

對于delete ,它應該能夠處理空指針的delete ,于是:

void delete_memory(void* ptr)
{
    free(ptr);
}

//自己編寫的delete
void operator delete (void* ptr) throw()
{
    if(ptr == nullptr)  return; //如果是空指針,那麼直接傳回
    delete_memory(ptr);
}
           

這是一般的new 和delete。

現在我們探讨一下class 的new 和delete。

class Base
{
public:
    static void* operator new (size_t size)
    {
        //...
    }
    static void operator delete (void* ptr, size_t size ) throw() 
    {
        //...
    }
};
           

現在,如果有class 繼承了Base,那麼它也會繼承Base 的new。

class Derived:public Base

//...

Derived* pd = new Derived; //調用了base版本的new
           

這時候如果new 了Derived class,它會調用base 版本的new ,這明顯不是我們想要的。

是以,class 專屬版本的new 還應該能夠處理大小和自身不同的記憶體申請要求。[1]

class Base
{
public:
    static void* operator new (size_t size)
    {
        if(size != sizeof(Base))
            ::operator new(size);  //如果大小不等于自身,交給全局的new
        else 
            return get_memory(size);
    }
    static void operator delete (void* ptr, size_t size ) throw() 
    {
        if(ptr== nullptr) return;
        if(size != sizeof(Base)) return ::operator delete(ptr);
        delete_memory(ptr);
    }
};
           

當然這裡面的情況更加複雜一點,如果Base 和Derived 的sizeof 大小是一樣的呢?

如果從不會成為基類的類的new,就可以不用檢驗與自身大小不一緻的new ?

我覺得,最好的解決方案是視自己的需求而定,沒有最好的技術方案,隻有最合适的技術方案。

placement new 與 placement delete

關于placement new 和placement delete 的若幹術語[1]:

placement new: 如果operator new 接受的參數除了一定會有的那麼size_t 之外,還有其他,這就是所謂的placement new

placement delete : 如果operator delete 接受額外的參數,便稱為placement delete

當new 一個對象的時候:

這一過程如下:

  • 配置設定記憶體
  • 調用Some_class 的構造函數
  • 傳回指針值

如果在調用Some_class 的構造函數中,抛出異常怎麼辦?

在這個時候,我們是沒有辦法顯示地去回收記憶體。那這個職責就落到了運作期系統身上。運作期系統會調用與operate new 相應的operator delete 回收記憶體。

那如果運作期系統找不到相應的delete 函數呢?它兩手一攤,表示我無能為力。那這個時候,就發生臭名昭彰的記憶體洩露了。

就正常的operator new 函數,原型如下[1]:

void* operator new (size_t size) throw(bad_alloc);
           

對應的operator delete 如下:

void operator delete(void* ptr) throw(); //global 中正常的簽名式
void operator delete(void* ptr, size_t size) throw();//class 作用域中典型的簽名式
           

那如果new 的時候調用了placement new 函數,就placement delete 函數就要存在,否則在應對new 抛出異常的情況下,就無法回收記憶體了。

假設有個類,它重載operator new ,并額外接受一個參數用來輸出一些資訊,像這樣:

class Some_class
{
public:
    static void* operator new (size_t s, ostream& out) throw(bad_alloc)
    {
        out<<" Some_class is new "<<endl;
        return malloc(s);
    }
};
           

其中的new 函數就是placement new。

那它也要有相對應的placement delete函數:

static void operator delete (void* ptr, ostream& out) throw();
           

那現在這個類的定義如下:

class Some_class
{
public:
    static void* operator new (size_t s, ostream& out) throw(bad_alloc)
    {
        out<<" Some_class is new "<<endl;
        return malloc(s);
    }
    static void operator delete (void* ptr, ostream& out) throw()
    {
        out<<" Some_class is delete "<<endl;
        return free(ptr);
    }
};
           

一切都很好,現在我們來試着使用一下這個類:

SomeClass* ps = new (cout) Some_class;//ok
delete ps;//error
           

然後,發現,new 是正常的,但是delete 的時候卻報錯了。

那是因為placement delete 隻有在“伴随placement new 調用而出發的構造函數出現異常時”才會被調用。[1]

為了了解,不妨再看一遍加粗的字。

也就是說,把placement delete 寫出來并不是為了我們自己調用,而是為了解決“伴随placement new 調用而出發的構造函數出現異常時”回收記憶體的問題。它隻是我們編寫過程中的一個安全保證。

那麼,delete 一個指針的時候,調用的是正常的delete函數。但是上面的類的new 和 delete 聲明,掩蓋掉了global 作用域下的new 和 delete 函數,是以,我們需要重新聲明:

class Some_class
{
public:
    static void* operator new (size_t s, ostream& out) throw(bad_alloc)
    {
        out<<" Some_class is new "<<endl;
        return malloc(s);
    }
    static void operator delete (void* ptr, ostream& out) throw()
    {
        out<<" Some_class is delete "<<endl;
        return free(ptr);
    }
    static void operator delete (void* ptr) throw()
    {
        cout<<" ok ,now is normal delete!"<<endl;
        return free(ptr);
    }
};
           

事實上,正常的new 也被掩蓋了。也就是說,如果我們這個使用代碼:

也會報錯。

為了解決class 版本下,需要額外定義使用placement new 和delete ,我們可以聲明一個基類,然後讓别的類繼承它,使用using 使得函數可見,就ok了:

class Stander_new_delete_forms
{
public:
    // normal new delete
    static void* operator new (size_t size) throw(bad_alloc) 
    {   return ::operator new(size); }
    static void operator delete (void* ptr) throw()
    {   return ::operator delete(ptr); }

    //placement new delete
    static void* operator new (size_t size, void* ptr) throw()
    {   return ::operator new(size,ptr); }
    static void operator delete (void* p_memory, void* ptr) throw()
    {   return ::operator delete(p_memory, ptr); }

    //nothrow new delete
    static void* operator new(size_t size, const nothrow_t& nt) throw()
    {   return ::operator new(size, nt); }
    static void operator delete(void* ptr, const nothrow_t& nt) throw()
    {   return ::operator delete(ptr, nt); }
};


class Some_class: public Stander_new_delete_forms
{
public:
    using Stander_new_delete_forms::operator new; //使用using
    using Stander_new_delete_forms::operator delete;
    static void* operator new (size_t s, ostream& out) throw(bad_alloc)
    {
        out<<" Some_class is new "<<endl;
        return malloc(s);
    }
    static void operator delete (void* ptr, ostream& out) throw()
    {
        out<<" Some_class is delete "<<endl;
        return free(ptr);
    }
};
           

global 作用域下的new 和 delete

global 作用域下提供了三對的new 和 delete。

void* operator new (size_t size) throw(bad_alloc) 
void* operator new (size_t size, void* ptr) throw()
void* operator new(size_t size, const nothrow_t& nt) throw()


void operator delete (void* ptr) throw()
void operator delete (void* p_memory, void* ptr) throw()
void operator delete(void* ptr,const nothrow_t& nt) throw()
           

分别對應normal new、placement new 和 nothrow new。

normal new 就不提了,經常使用的就是它。

placement new 的是用于指向一個對象該被構造之處。

一個對象的記憶體通常是固定的,這個版本的作用就是在一個已經開辟記憶體的對象上,重新構造對象。

有點像在廢舊的城池上重建立立江山的意思。使用如下:

Some_class* ps = new Some_class;
    ps->~Some_class(); //廢舊的城池
    void* pv = ps;
    ps = new (pv) Some_class; //重新利用
    delete ps;
           

nothrow 的意思就是new 過程中不抛出異常。如果記憶體配置設定不成功,那麼傳回空指針。

Some_class* pns = new (nothrow) Some_class;
    if(pns != nullptr)
        //...
           

[參考資料]

[1] Scott Meyers 著, 侯捷譯. Effective C++ 中文版: 改善程式技術與設計思維的 55 個有效做法[M]. 電子工業出版社, 2011.

(條款49:了解new_handler 行為;

條款51:編寫new 和 delete 時需固守正常;

條款52:寫了placement new 也要寫placement delete)

c++

繼續閱讀