運算符new的使用,事實上經過兩個步驟完成:
int* pi = new int(5);
//1.通過new運算符,配置所需要的記憶體
int* pi =_new(sizeof(int));
//2.将配置的對象設立初值
*pi = 5;
更進一步說,初始化操作應該再記憶體成功(經由new運算符)後才執行:
//new運算符的兩個分離步驟
//int* pi = new int(5);
//重寫聲明
int* pi;
if(pi = _new(sizeof(int)))
*pi = 5;
//delete的分離步走
//delete pi;
//重寫聲明
if(pi != 0)
_delete(pi);
如果pi的值時0,C++語言會要求delete運算符不會執行,pi并不會自動清0,像下面這樣的後繼行為,雖然沒有良好定義,但是可能(也可能不)被評估為真,這是因為對于pi所指之記憶體的變更或再使用,可能(也可能不)會發生。
if(pi && pi == 5) ...
pi所指對象的生命會因delete而結束,是以後繼任何對pi的參考操作就不再包良好的行為,并是以被視為一種不好的程式風格。然而,把pi繼續當一個指針來用,仍然可以的(雖然使用受到限制),例如:
//ok,pi仍然指向合法的空間
//甚至幾十存儲其中的object不再合法
if(pi == sentinel) ...
在這裡,使用指針pi,和使用pi所指的對象,其差别在于哪一個生命已經結束了。雖然該位址的對象不再合法,位址本身卻仍然代表一個合法的程式空間,是以pi能夠繼續使用,但隻能在受限制的情況下使用,很想一個void*指針的情況。
以constructor來配置一個class object情況類似:
//被轉化為
Point3d origin;
//C++ pseudo code
if(origin = _new(sizeof(Point3d)))
origin = Point3d::Point3d(origin);
如果實作出exception handling,那麼轉換結果可能複雜些:
//C++ pseudo code
if(origin = _new(sizeof(Point3d)))
{
try{
origin = Point3d::Point3d(origin);
}
catch(...){
//調用delete library funct釋放記憶體
_delete(origin);
//将原來的exception上傳
throw;
}
}
destructor應用類似:
delete origin;
//被内部轉化
if(origin != 0)
{
Point3d::~Point3d(origin);
_delete(origin);
}
以下是library中new的實作方法(以下版本并沒考慮exception handing):
extern void*
operator new(size_t size)
{
if(size == 0)
size = 1;
void* last_alloc;
while(!(last_alloc = malloc(size)))
{
if(_new_handler)
(*_new_handler)();
else
return 0;
}
return last_alloc;
}
雖然如下寫法是合法的:
new T[0];
但語言要求每一次對new的調用都必須傳回一個獨一無二的指針,解決此問題的方法是傳統的傳回一個指針,指向一個預設為1byte的記憶體空間,另一個有趣之處是,它允許使用者提供一個屬于自的_new_handler()函數。
new運算符實際上總是以标準C malloc完成,雖然并沒有規定一定得這麼做。相同情況下,delete運算符也是以标準C的free()完成:
extern void
operator delete(void* ptr)
{
if(ptr)
free((char*)ptr);
}
針對數組的new語意
當我們寫:
int* p_array = new int[5];
vec_new()不會被真正調用,因為它主要功能是把default constructor施行于class object所組成的數組的每一個元素上。倒是new運算符會被調用:
int* p_array = (int*)_new(5*sizeof(int));
相同情況下,如果我們寫:
//struct simple_aggr{float f1,f2;};
simple_aggr* p_aggr = new simple_aggr[5];
vec_new()也不會被調用,因為simple_aggr并沒有定義一個constructor或destructor,是以配置數組以及清理p_aggr數組的操作,隻是單純地擷取記憶體和釋放記憶體而已。
然而如果class定義了一個default constructor,某些版本的vec_new()就會被調用,配置并構造class objects所組成的數組:
Point3d* p_array = new Point3d[10];
//内部轉化
Point3d* p_array;
p_array = vec_new(0,sizeof(Point3d),10,
&Point3d::Point3d,
&Point3d::~Point3d);
在個别數組構造過程中,如果發生exception,destructor就會被傳遞給vec_new()。隻要已經構造妥當的元素才施行destructor。
對于寫下如下的代碼,析構函數對應的寫法:
int array_size = 10;
Point3d* p_array = new Point3d[10];
//C++2.0之前必須這麼寫
delete [array_size] p_array;
//C++2.0之後可以寫麼寫
delete [] p_array;
為了回溯相容,兩種形式都可以接受。
對于如下的destructor操作而言:
delete p_array;
那麼隻有第一個元素被析構,其他元素仍然存在——雖然其相關的記憶體已經歸還了。
Placement Operator new的語意
有一個預先定義好的重載的(overloaded)new運算符,成為placement operator new。它需要第二個參數,類型為void*,調用方式如下:
Poin2w* ptw = new(arena)Poin2w;
其中arena指向記憶體中的一塊區域,用以放置新産生出來的Point2w object。它的實作方法隻要将“獲得的指針(arena)”所指的位址傳回即可:
void* operator new(size_t,void* p)
{
return p;
}
Placement new operator所擴充的另一半是将Point2w constructor自動實施于arena所指的位址上:
//C++ pseudo code
Poin2w* ptw = (Poin2w*)arena;
if(ptw != 0)
ptw->Point2w::Point2w();
然而卻有一個輕微不良行為:
//讓arena成為全局性定義
vod foobar()
{
Point2w* p2w = new(arena)Point2w;
//do it
//now manipulate a new object
p2w = new(arena)Point2w;
}
如果placement operator在原已經存在的一個object上構造新的object,該object有一個destructor,這個destructor并不會被調用,調用該destructor的方法之一是将那個指針delete。不過在此例中如果這樣做是錯誤的:
delete p2w;
p2w = new(arena)Point2w;
//施行destructor的正确方法
p2w->~Point2w;
p2w = new(arena)Point2w;
剩下的問題有一個是設計的問題:在例子中第placement operator第一次調用,會将新object構造于原已存在的object至上,還是會析構于全新位址上?我們如何知道arena所指的這塊區域是否需要先析構?這個問題在語言層面沒有解答,一個合理的習慣是指向new這一段也要負責執行destructor。
另一個問題關系到arena所表現的類型,C++ Standard說它必須指向相同類型的class,要不就是一塊“新鮮”記憶體,足夠容納該類型的object。但是,derived class并不被支援。對于一個derived class,或是其他沒有關聯的類,其行為雖然并非不合法,卻是未定義的。
新的記憶體空間可以這樣配置:
char* arena = new char[sizeof(Point2w)];
相同類型的object可以這樣獲得:
Poin2w* arena = new Point2w;
不論哪種情況,新的Point2w的存儲空間的确覆寫了arena的位置,此行為良好。然而,placement new operator并不支援多态。對于derived class比其base class大的話,derived class 的constructor會被破壞。
還有一個問題是:
struct Base{int j;virtual void f();};
struct Derived: Base{ void f();};
void fooBar()
{
Base b;
b.f(); //Base::f()被調用
b.~Base();
new(&b)Derived;
b.f(); //哪一個f()被調用
}
由于上述兩個classes有相同的大小,把derived object放在base class而配置的記憶體中是安全的,然而,要支援這一點,就必須放棄動态的virtual function。盡管大部分使用者以為調用時Derived的,但大部分編譯器調用卻是Base。