程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> 關於C++ >> C++對象模型——new 和 delete 運算符(第六章)

C++對象模型——new 和 delete 運算符(第六章)

編輯:關於C++

6.2 new 和 delete 運算符

運算符 new 的使用,看起來似乎是個單一運算,像這樣:
int *pi = new int(5);
但事實上它是由以下兩個步驟完成:
1.通過適當的 new 運算符函數實體,配置所需的內存:
// 調用函數庫中的new運算符
int *pi = __new(sizeof(int));
2.給配置得來的對象設立初值:
*pi = 5;
更進一步地,初始化操作應該在內存配置成功(經由 new 運算符)後才執行:
// new運算符的兩個分離步驟
// given: int *pi = new int(5);
// 重寫聲明
int *pi;
if (pi = __new(sizeof(int)))
    *pi = 5;
delete 運算符的情況類似,當程序員寫下:
delete pi;
時,如果pi的值是0,C++語言會要求 delete 運算符不要有操作.因此,編譯器必須為此調用構造一層保護膜:
if (pi != 0)
    __delete(pi);
請注意pi並不會因此被自動清除為0,因此像這樣的後繼行為:
// 沒有良好的定義,但是合法
if (pi && *pi == 5)
    ...
雖然沒有良好的定義,但是可能(也可能不)被評估為真.這是因為對於pi所指向的內存的變更或再使用,可能(也可能不)會發生.
pi所指對象的生命會因 delete 而結束,所以後繼任何對pi的參考操作就不再保證有良好的行為,並因此被視為是一種不好的程序風格.然而,把pi繼續當做一個指針來用,仍然是可以的(雖然其使用受到限制),例如:
// pi仍然指向合法空間
// 甚至即使儲存於其中的object已經不再合法
if (pi == sentine1)
    ...
在這裡,使用指針pi和使用pi所指的對象,其差別在於哪一個的聲明已經結束了.雖然該地址上的對象不再合法,但地址本身卻仍然代表一個合法的程序空間.因此pi能夠繼續被使用,但只能在受限制的情況下,很像一個 void * 指針的情況.
以constructor來配置一個 class object,情況類似,例如:
Point3d *origin = new Point3d;
被轉換為:
Point3d *origin;
if (origin = __new(sizeof(Point3d)))
    origin = Point3d::Point3d(origin);
如果exception handling的情況下,destructor應該被放在一個try區段中.exception handler會調用 delete 運算符,然後再一次丟出該exception.
一般的lirary對於 new 運算符的實現操作都是很直接了當,但有兩個精巧之處值得斟酌:
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 的調用都必須傳回一個獨一無二的指針.解決該問題的傳統方法是傳回一個指針,指向一個默認為1 byte的內存區塊(這就是為什麼程序代碼中的size被設為1的原因).這個實現技術的另一個有趣之處是,它允許使用者提供一個屬於自己的_new_handler()函數.這正是為什麼每一次循環都調用_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 objects所組成的數組的每一個元素上.倒是 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數組的操作,只是單純地獲得內存和釋放內存而已.這些操作由 new 和 delete 運算符來完成就綽綽有余了.
然而如果 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的施行,因為它們的內存已經被配置出來了,vec_new()有責任在exception發生的時候把那些內存釋放掉.
在 delete 時不需要指定數組元素的數目,如下所示:
delete []p_array;
尋找數組維度給 delete 運算符的效率帶來極大的影響,所以才導致這樣的妥協:只有在中括號出現時,編譯器才尋找數組的維度,否則它便假設只有單獨一個object要被刪除.如果程序員沒有提供必須的中括號,像這樣:
delete p_array;
那麼就只有第一個元素會被解構,其他元素仍然存在.
應該如何記錄元素數目?一個明顯的方法就是為vec_new()所傳回的每一個內存區塊配置一個額外的word,然後把元素數目包藏在那個word中.通常這種被包藏的數值稱為所謂的cookie.然而Sun編譯器卻維護一個聯合數組,放置指針以及大小.它也把destructor的地址維護於此數組中.
cookie策略有一個普遍引起憂慮的話題,那就是如果一個壞指針被交給delete_vec(),取出來的cookie自然是不合法的.一個不合法的元素數目和一個壞的起始地址,會導致destructor以非預期的次數被施行於一段非預期的區域,然而在聯合數組的政策下,壞指針的可能結果就只是取出錯誤的元素數目而已.


 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved