string *stringArray = new string[100];
...
delete stringArray;
stringArray指向的100個string對象中的99個不會被正確地摧毀,因爲他們的析構函數永遠不會被調用。用new的時候會發生兩件事。首先,內存被分配,然後,爲被分配的內存調用一個或多個構造函數。用delete的時候,也有兩件事發生:首先,爲將被釋放的內存調用一個或多個析構函數,然後,釋放內存。
string *stringPtr1 = new string;
string *stringPtr2 = new string[100];
...
delete stringPtr1; // 刪除一個對象
delete [] stringPtr2; // 刪除對象數組
如果你在stringPtr1前加了"[]"會怎樣呢?答案是:那將是不可預測的;如果你沒在stringPtr2前沒加上"[]"又會怎樣呢?答案也是:不可預測。所以,解決這類問題的規則很簡單:如果你調用new時用了[],調用delete時也要用[]。如果調用new時沒有用[],那調用delete時也不要用[]這個規則對喜歡用typedef的人來說也很重要,因爲寫typedef 的程序員必須告訴別人,用new創建了一個typedef 定義的類型的對象後,該用什麼形式的delete來刪除。
typedef string AddressLines[4]; // 一個人的地址,共4行,
//每行一個string
string *pal = new AddressLines; // 注意"new AddressLines"
//返回string*, 和
// "new string[4]"返回的一樣
delete pal; // 錯誤!
delete [] pal; // 正確
爲了避免混亂,最好杜絕對數組類型用typedefs。這其實很容易,因爲標準C++庫(見條款49)包含有stirng和vector模板,使用他們將會使對數組的需求減少到幾乎零。
條款6:析構函數裏對指針成員調用delete
如果在構造函數裏忘了初始化某個指針,或者在賦值操作的過程中忘了處理它,問題會出現得很快,很明顯,所以在實踐中這兩個問題不會那麼折磨你。但是,如果在析構函數裏沒有刪除指針,它不會表現出很明顯的外部症狀。相反,它可能只是表現爲一點微小的內存泄露,並且不斷增長,最後吞噬了你的地址空間,導致程序夭折。因爲這種情況經常不那麼引人注意,所以每增加一個指針成員到類裏時一定要記清楚。
刪除空指針是安全的,在你的析構函數裏你就可以只用簡單地delete掉他們,而不用擔心他們是不是被new過。
你當然不會用delete去刪除一個沒有用new來初始化的指針,你也永遠不會去刪除一個傳遞給你的指針。換句話說,除非類成員最初用了new,否則是不用在析構函數裏用delete的。
說到智能指針,這裏介紹一種避免必須刪除指針成員的方法,即把這些成員用智能指針對象來代替。
條款7:預先準備好內存不夠的情況
operator new在無法完成內存分配請求時會拋出異常。你有時會不去管它,也許一直沒去管它。但你心裏一定還是深深地隱藏着一種罪惡感:萬一new真的產生了異常怎麼辦?
一種解決方案:
#define NEW(PTR, TYPE) \
try { (PTR) = new TYPE; } \
catch (std::bad_alloc&) { assert(0); }
說明:bad_alloc是operator new不能滿足內存分配請求時拋出的異常類型。assert是個宏。這個宏檢查傳給它的表達式是否非零,如果不是非零值,就會發出一條出錯信息並調用abort。assert 只是在沒定義標準宏NDEBUG的時候,即在調試狀態下才這麼做。
問題:1.具有宏的通病,2.沒有考慮到new有各種各樣的使用方式。比如:
new T;
new T(constructor arguments);
new T[size];
另一種解決方案:
當內存分配請求不能滿足時,調用你預先指定的一個出錯處理函數。
這個方法基於一個常規,即當operator new不能滿足請求時,會在拋出異常之前調用客戶指定的一個出錯處理函數——一般稱爲new-handler函數。
typedef void (*new_handler)();
new_handler set_new_handler(new_handler p) throw();
set_new_handler的
輸入參數是operator new分配內存失敗時要調用的(新的)出錯處理函數的指針,
返回值是set_new_handler 沒調用之前就已經在起作用的
舊的出錯處理函數的指針。
使用方法:
// function to call if operator new can't allocate enough memory
void noMoreMemory()
{
cerr << "Unable to satisfy request for memory\n";
abort();
}
int main()
{
set_new_handler(noMoreMemory);
int *pBigDataArray = new int[100000000];
...
}
一個設計得好的new-handler函數必須實現下面功能中的一種:
- 產生更多的可用內存
- 安裝另一個不同的new-handler函數
- 卸除new-handler
- 拋出std::bad_alloc或從std::bad_alloc繼承的其他類型的異常
- 沒有返回
在類中使用:
class X {
public:
static new_handler set_new_handler(new_handler p);
static void * operator new(size_t size);
private:
static new_handler currentHandler;
};
new_handler X::currentHandler; // 缺省設置currentHandler爲0(即null)
new_handler X::set_new_handler(new_handler p)
{
new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
最後看看X的operator new所做的:
- 調用標準set_new_handler函數,輸入參數爲X的出錯處理函數。
- 調用全局operator new分配內存。
- 假設全局operator new爲類型X的對象分配內存成功,, X的operator new會再次調用標準set_new_handler 來恢復最初的全局出錯處理函數。
實現:
void * X::operator new(size_t size)
{
new_handler globalHandler = // 安裝X的new_handler
std::set_new_handler(currentHandler);
void *memory;
try { // 嘗試分配內存
memory = ::operator new(size);
}
catch (std::bad_alloc&) { // 恢復舊的new_handler
std::set_new_handler(globalHandler);
throw; // 拋出異常
}
std::set_new_handler(globalHandler); // 恢復舊的new_handler
return memory;
}
使用:
void noMoreMemory(); // X的對象分配內存失敗時調用的
// new_handler 函數的聲明
//
X::set_new_handler(noMoreMemory);
// 把noMoreMemory設置爲X的
// new-handling函數
X *px1 = new X; // 如內存分配失敗,
// 調用noMoreMemory
string *ps = new string; // 如內存分配失敗,
// 調用全局new-handling函數
//
X::set_new_handler(0); // 設X的new-handling函數爲空
//
X *px2 = new X; // 如內存分配失敗,立即拋出異常
// (類X沒有new-handling函數)
使用模板類:
定義:
emplate<class T> // 提供類set_new_handler支持的
class NewHandlerSupport { // “混合風格”的基類
public:
static new_handler set_new_handler(new_handler p);
static void * operator new(size_t size);
private:
static new_handler currentHandler;
};
template<class T>
new_handler NewHandlerSupport<T>::set_new_handler(new_handler p)
{
new_handler oldHandler = currentHandler;
currentHandler = p;
return oldHandler;
}
template<class T>
void * NewHandlerSupport<T>::operator new(size_t size)
{
new_handler globalHandler =
std::set_new_handler(currentHandler);
void *memory;
try {
memory = ::operator new(size);
}
catch (std::bad_alloc&) {
std::set_new_handler(globalHandler);
throw;
}
std::set_new_handler(globalHandler);
return memory;
}
// this sets each currentHandler to 0
template<class T>
new_handler NewHandlerSupport<T>::currentHandler;
使用:有了這個模板類,對類X加上set_new_handler功能就很簡單了:只要讓X從newHandlerSupport<X>繼承:
// note inheritance from mixin base class template. (See
// my article on counting objects for information on why
// private inheritance might be preferable here.)
class X: public NewHandlerSupport<X> {
... // as before, but no declarations for
}; // set_new_handler or operator new
不管是用“正規”(即拋出異常)形式的new還是“無拋出”形式的new,重要的是你必須爲內存分配失敗做好準備。!