在C++中,分配和歸還內存時需要保持數組和非數組形式的操作符的匹配:
T *aT = new T; // 非數組
T *arrT = new T[16]; // 數組
delete aT; // 非數組
delete [] arrT; // 數組
aT = new T[1]; // 數組
delete aT; // 錯誤!應該采用數組形式的操作符
保持這些函數正確地成對使用很重要,因為數組的分配和歸還所使用的函數不同於非數組形式。對於數組而言,new表達式不是使用operator new為數組分配存儲區,而是使用array new。類似地,delete表達式也不是調用operator delete來釋放數組的存儲區,而是調用array delete。更確切的說,當分配一個數組時應該使用一個不同的操作符,即new[],而不是像分配一個非數組的對象那樣使用new操作符。規劃內存的情形類似。
array new和array delete分別是operator new和operator delete數組版本的對應物,它們的聲明方式類似:
void* operator new(size_t) throw(bad_alloc); // operator new
void* operator new[](size_t) throw(bad_alloc); // array new
void operator delete(void*) throw(bad_alloc); // operator delete
void operator delete[](void*) throw(bad_alloc); // array delete
有關這些函數的數組形式最常出現的混亂情形,出現於一個特定的類或類層次結構使用成員operator new和operator delete定義了自己的內存管理方式時:
class Handle
{
public:
// ...
void* operator new(size_t);
void operator delete(void*);
// ...
};
Handle類定義了非數組形式的內存管理函數,但它們並不被用於Handle數組的情形,對於數組的情形,調用的是全局array new和array delete:
Handle* arrHandle = new Handle[MAX]; // 調用::operator new[]
// ...
delete [] arrHandle; // 調用::operator delete[]
從邏輯上來說,只要聲明了非數組形式的函數(即operator new和operator delete),就應該為這些函數聲明數組的形式,在日常編程實踐中,這一點往往被忽視。如果目的是想調用全局的數組分配操作,那麼,定義“僅僅轉發對全局形式的調用”的局部形式,可以讓事情變得更清晰:
class Handle
{
public:
// ...
void* operator new(size_t);
void operator delete(void*);
void* operator new[](size_t n)
{
return ::operator new(n);
}
void operator delete[](void* p)
{
::operator delete(p);
}
// ...
};
如果目的是禁止分配Handle數組,那麼可以將數組形式的函數聲明為private並且不提供定義。
另外一個易產生混亂和錯誤的地方在於,傳遞給array new的那個表示大小的參數值,取決於函數是如何被調用的。當operator new在一個new表達式中被隱式地調用時,編譯器會決定需要多少內存,並將該數量作為參數傳遞給operator new。這個數量就是正在分配的對象的大小:
aT = new T; // 調用operator new(sizeof(T());
也可以直接調用operator new,在這種情況下,必須明確指明希望分配的字節數:
aT = static_cast<T*>(operator new(sizeof(T)));
也可以直接調用array new:
arrT = static_cast<T*>(operator new[](sizeof(T) * 16));
然而,當通過new表達式隱式地調用array new時,編譯器常常為略微增加一些內存請求:
arrT = new T[16]; // 請求內存為* sizeof(T) + delta字節
所請求的額外空間一般由運行期內存管理器(runtime memory manager)來記錄關於數組的一些信息,這些信息(包括分配的元素個數、每個元素的大小等)對於以後回收內存是必不可少的。不過,事情遠沒有這麼簡單,編譯器未必為每一個數組分配都請求額外的內存空間,並且對於不同的數組分配而言,額外請求的內存空間大小也會發生變化。
請求內存數量上的區別通常只有在編寫非常底層的代碼時才需要考慮,在這種情形下,數組的存儲區被直接處理。如果打算編寫底層代碼,通常最簡單的做法是避免直接調用array new以及編譯器所執行的有關干預,取而代之的是,使用普通的operator new。