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)