1. 程式人生 > >深入探索C++物件模型(五)

深入探索C++物件模型(五)

https://www.cnblogs.com/lengender-12/p/6970496.html
構造、解構、拷貝語意學(Semantics of Construction,Destruction, and Copy)
一般而言,class的data member應該被初始化,並且只在constructor中或是在class的其他member functions中指定初值。其他任何操作都將破壞封裝性質,使class的維護和修改更加困難。

純虛擬函式的存在(Presence of a Pure Virtual Function)
C++ 新手常常很驚訝地發現,一個人竟然可以定義和呼叫(invoke)一個pure virtual function:不過它只能被靜態地呼叫(invoked statically),不能經由虛擬機制呼叫。例如,你可以合法地寫下這段程式碼:

//定義pure virtual function但只能被靜態地呼叫(invoked statically)
inline void Abstract_base::interface() const{
//請注意,先前曾宣告這是一個pure virtual const
function
//…
}

inline void Concrete_derived::interface() const{
//靜態呼叫(static invocation)
Abastract_base::interface();
//請注意,我們竟然能夠呼叫一個pure virtual
function
//…
}
要不要這樣做,全由class設計者決定。唯一的例外就是pure virtual destructor: class設計者一定要定義它。為什麼? 因為每一個derived class destructor會被編譯器加以擴充套件,以靜態呼叫的方式呼叫其“每一個virtual base class”以及“上一層base class”的destructor。因此,只要缺乏任何一個base class destructor的定義,就會導致連結失敗。

“無繼承”情況下的物件構造
考慮下面的程式片段:

(1) Point global;
(2)
(3) Point foobar()
(4) {
(5) Point local;
(6) Point *heap = new Point;
(7) *head = local;
(8) //… stuff …
(9) delete heap;
(10) return local;
(11) }
L1,L5,L6表現出三種不同的物件產生方式:global記憶體配置、local記憶體配置和heap記憶體配置。L7把一個class object指定給另一個,L10設定返回值,L9則明確地以delete運算子刪除heap object.

一個object的生命,是該object的一個執行期屬性。local object的宣告從L5的定義開始,到L10為止。global object的生命和整個程式的生命相同。heap object的生命從它被new運算子配置出來開始,到它被delete運算子摧毀為止。

下面是Point的第一次宣告,可以寫成C程式,C++ standard說這是一種所謂的Plain old Data宣告形式:

typedef struct{
float x, y, z;
}Point;
如果以C++ 來編譯這段碼,會發生什麼事? 觀念上,編譯器會為Point宣告一個trivial default constructor、一個trivial destructor、一個trivial copy constructor,以及一個trivial copy assignment operator。但實際上,編譯器會分析這個宣告,併為它貼上Plain of Data標籤

當編譯器遇到這樣的定義:

(1) Point global;
時,觀念上Point的trival constructor和destructor都會被產生並被呼叫,constructor在程式起始(startup)處被呼叫而destructor在程式的exit()處被呼叫。然而,事實上那些tirvial members要不是沒被定義,就是沒被呼叫,程式的行為一如它在C中的表現一樣。

只有一個小小的例外,在C中,global被視為一個“臨時性的定義”,因為它沒有明確的初始化操作。一個“臨時性的定義”可以在程式中發生多次,那些例項會被連結器摺疊起來,只留下單獨一個實體,被放在程式data segment中的一個“特別保留給未初始化之global objects使用”的空間,由於歷史的緣故,這段空間被稱為BSS,這是Block Started by Symbol的縮寫。

C++ 並不支援“臨時性的定義”,這是因為class構造行為的隱含應用之故。因此,global在C++ 中被視為完全定義(它會阻止第二個或更多個定義)。C和C++的一個差異就在於,BSS data segment在C++中相對地不重要。C++ 的所有全域性物件都被當作“初始化過的資料”來對待。

foobar() 函式中的L5,有一個Point object local,同樣也是既沒有被構造也沒有被解構。當然啦,Point object local如果沒有先經過初始化,可能會成為一個潛在的程式臭蟲——萬一第一次使用它就需要其賦初值的話(如L7)。至於heap object在L6的初始化操作:

(6) Point *heap = new Point;
會被轉換為對new運算子的呼叫:

Point *heap = _new(sizeof(Point));
再一次強調,並沒有default constructor施行與new運算子所傳回的Point object身上。L7對此object有一個賦值(賦值,assign)操作,如果local曾被適當地初始化過,一切就沒有問題:

(7) *heap = local;
事實上這一行會產生編譯警告如下:

warning,line 7, local is used before being initialized
觀念上,這樣的指定操作會觸發trivial copy assignment operator進行拷貝搬運操作。然而實際上此object是一個Plain old data,所以賦值操作(assignment)將只是像C那樣的純粹位搬移操作。L9執行一個delete操作:

(9) delete heap;
會被轉換為對delete運算子(由library提供)的呼叫:

_delete(heap);
觀念上,這樣的操作會觸發Point的trivial destructor。但是一如我們所見,destructor要不是沒有被產生就是沒有被呼叫。最後,函式以傳值(by value)的方式將local當作返回值傳回,這在觀念上會觸發trivial copy constructor,不過實際上return操作只是一個簡單的位拷貝操作,因為物件是一個Plain old data。

抽象資料型別(Abstract Data Type)
以下是Point的第二次宣告,在public介面之下多了private資料,提供完整的封裝性,但是沒有提供virtual function:

class Point{
public:
Point(float x = 0.0, float y = 0.0, float z = 0.0)
: _x(x), _y(y), _z(y) { }

//no copy constructor, copy operator or destructor defined

private:
float _x, _y, _z;
};
這個經過封裝的Point class,其大小並沒有改變,還是三個連續的float。是的,不論private、public存取層,或是member function的宣告,都不會佔用額外的物件空間。

對於一個global實體:

Point global; //實施Point::Point(0.0, 0.0, 0.0)
現在有了default constructor作用於其上。由於global被定義在全域性範疇中,其初始化操作將延遲到程式啟用(startup)時才開始。

如果要對class中的所有成員都設定常量初值,那麼給予一個explicit initialization list會比較高效(比起意義相同的constructor的inline expansion而言)。甚至在local scope中也是如此。舉例如下:

void mumble(){
Point local1 = {1.0, 1.0, 1.0};

Plint local2;

//相當於一個inline expansion, explicit initialization會稍微快一些
local2._x = 1.0;
local2._y = 1.0;
local2._z = 1.0;

}
local1的初始化操作會比local2的高效,這是因為當函式的activation record被放程序序堆疊時,上述initialization list中的常量就可以被放進local1記憶體中了。

Explicit initialization list帶來三項缺點:

只有當class members都是public,此法才奏效
只能指定常量,因為它們在編譯時期就可以被評估求值(evaluated)
由於編譯器並沒有自動施行之,所以初始化行為的失敗可能性會高一些
在編譯器層面,會有一個優化機制用來識別inline constructors,後者簡單地提供一個member-by-member的常量指定操作。然後編譯器會抽取出那些值,並且對待它們就好像是explicit initialization list所供應的一樣,而不會把constructor擴充套件成一系列的assignment指令。

local Point object的定義如下:

{
Point local;
//…
}
現在被附加上default Point constructor的inline expansion:

{
//inline expansion of default constructor
Point local;
local._x = 0.0, local._y = 0.0, local._z = 0.0;
//…
}
L6配置出一個heap Point object:

(6) Point *heap = new Point;
現在則被附加一個“對default Point Constructor的有條件呼叫操作”:

Point *heap = _new(sizeof(Point));
if(heap != 0)
heap->Point::Point();
然後又被編譯器進行inline expansion操作,至於把heap指標指向local object:

(7) *heap = local;
則保持簡單的位拷貝操作,以傳值方式傳回local object,情況也是一樣:

(10) return local;
L9刪除heap所指之物件:

(9) delete heap;
該操作並不會導致destructor被呼叫,因為我們並沒有明確地提供一個destructor函式實體。

觀念上,我們的Point class有一個相關得default copy constructor,copy operator和destructor,然而它們都是無關痛癢的(trivial),而且編譯器實際上根本沒有產生它們。

為繼承做準備
以下是第三個Point宣告,將為“繼承性質”以及某些操作的動態決議(dynamic resolution)做準備,當前我們限制對z成員進行存取操作:

class Point{
public:
Point(float x = 0.0, float y = 0.0)
: _x(x), _y(y) { }

//no destructor, copy constructor or copy operator

virtual float z();

protected:
float _x, _y;
};
再次強調,沒有定義一個copy constructor、copy operator、destructor。我們所有的memebers都以數值來儲存,因為在程式層面的預設語意之下,行為良好。

virtual function的引入促使每一個Point object擁有一個virtual table pointer。這個指標提供給我們virtual介面的彈性,代價是:每一個object需要額外的一個word空間。

除了每一個class object多負擔一個vptr之外,virtual function的引入也引發編譯器對於我們的Point class產生膨脹作用:

我們所定義的constructor被附加了一些碼,以便使vptr初始化。這些碼必須附加在任何base class constructors的呼叫之後,但必須在任何由使用者(程式設計師)供應的碼之前。如:

Point* Point::Point(Point *this, float x, float y)
: _x(x), _y(y){

  //設定object的virtual table pointer
  this->_vptr_Point = _vtbl_Point;

  //擴充套件member initialization list
  this->_x = x;
  this->_y = y;

  //傳回this物件
  return this;
}

合成一個copy constructor和一個copy assignment operator,而且其操作不再是trivial(但implicit destructor仍然是trivial)。如果一個Point object被初始化或以一個derived class object賦值,那麼以位為基礎(bitwise)的操作可能會給vptr帶來非法設定。

//copy constructor的內部合成
inline Point* Point::Point(Point* this, const Point& rhs){
//設定object的virtual table pointer(vptr)
this->_vptr_Point = _vtbl_Point;

  //將rhs座標中的位連續拷貝到this物件
  //或是經由member assignment提供一個member...

  return this;
}

編譯器在優化狀態下可能會把object的連續內容拷貝到另一個object身上,而不會實現一個精確地“以成員為基礎(memberwise)”的賦值操作。C++ Standard要求編譯器儘量延遲nontrivial members的實際合成操作,直到真正遇到其使用場合為止。

一般而言,如果你的設計之中有許多函式都需要以傳值方式(by value)傳回一個local class object,例如像如下形式的一個算術運算:

T opeartor+(const T&, const T&){
T result;
//真正的工作在此…
return result;
}
此時提供一個copy constructor就比較合理——甚至即使default memberwise語意已經足夠,它的出現會觸發NRV優化。NRV優化後就不再需要呼叫copy constructor,因為運算結果已經被直接置於“將被傳回的object”體內了。

繼承體系下的物件構造
當我們定義一個object如下:

T object;
時,實際上會發生什麼事情呢? 如果T有一個constructor(不論是由user提供或是由編譯器合成),它會被呼叫。這很明顯,比較不明顯的是,constructor的呼叫真正伴隨了什麼?

Constructor可能內帶大量的隱藏碼,因為編譯器會擴充每一個constructor,擴充程度視class T的繼承體系而定。一般而言編譯器所做的擴充操作大約如下:

記錄在member initialization list中的data members初始化操作會被放進constructor的函式本身,並以members的宣告順序為順序。
如果有一個member並沒有出現在member initialization list中,但它有一個default constructor,那麼該default constructor必須被呼叫。
在那之前,如果class object有virtual functions, 它們必須被設定初值,指向適當的virtual tables.
在那之前,所有上一層的base class constructors必須被呼叫,以base class生宣告順序為順序(與member initialization list中的順序沒有關聯):
如果base class被列於member initialization list中,那麼任何明確指定的引數都應該被傳遞進去。、
如果base class沒有被列於member initialization list中,而它有default constructor(或default memberwise copy constructor),那麼就呼叫之。
如果base class是多重繼承下的第二或後繼的base class,那麼this指標必須有所調整。
在那之前,所有virtual base class constructors必須被呼叫,從左到右,從最深到最淺
如果class被列於member initialization list中,那麼如果有任何顯式指定的引數,都應該傳遞過去。若沒有列於list之中,而class有一個default constructor,亦應該呼叫之
此外,class中的每一個virtual base class subobject的偏移位置(offset)必須在執行期可被存取
如果class object是最底層(most-derived)的class,其constructors可能被呼叫,某些用以支援這一行為的機制必須被放進來。

在這一節中,我要從“C++ 語言對classes所保證的語意”這個角度來探討constructors擴充的必要性。我再次以Point為例,併為它增加一個copy constructor、一個copy operator、一個virtual destructor如下:

class Point{
public:
Point(float x = 0.0, float y = 0.0);
Point(const Point&); //copy constructor
Point& operator=(const Point&); //copy assignment operator
virtual ~Point(); //virtual destructor
virtual float z() { return 0.0; }
protected:
float _x, _y;
};
Line class的宣告和擴充結果如下,它由_begin和 _end兩個點構成:

class Line{
Point _begin, _end;
public:
Line(float = 0.0, float = 0.0, float = 0.0, float = 0.0);
Line(const Point&, const Point&);

draw();
//...

};
每一個explicit constructor都會被擴充以呼叫其兩個member class objects的constructors。如果我們定義constructor如下:

Line::Line(const Point& begin, const Point& end)
_end(end), _begin(begin) {}
它會被編譯器擴充並轉換為:

Line* Line::Line(Line *this, const Point& begin, const Point& end){
this->_begin.Point::Point(begin);
this->_end.Point::Point(end);
return this;
}
由於Point聲明瞭一個copy constructor、一個copy operator,以及一個destructor(本例為virtual),所以Line class的implicit copy constructor、copy operator和destructor都將有實際功能。(nontrival)

當程式設計師寫下:

Line a;
時,implicit Line destructor會被合成出來(如果Line派生自Point,那麼合成出來的destructor將會是virtual。然而由於Line只是內帶Point objects而非繼承自Point,所以被合成出來的destructor只是nontrivial而已)。在其中,它的member class objects的destructor會被呼叫(以其構造的相反順序):

inline Line::~Line(Line *this){
this->_end.Point::~Point();
this->_begin.Point::~Point();
}
當然,如果Point destructor是inline函式,那麼每一個呼叫操作會在呼叫地點被擴展出來。請注意,雖然Point destructor是virtual,但其呼叫操作(在containing class destructor之中)會被靜態地決議出來(resolved statically)。

虛擬繼承(Virtual Inheritance)
考慮下面這個虛擬繼承:

class Point3d : public virtual Point{
public:
Point3d(float x = 0.0, float y = 0.0, float z = 0.0)
: Point(x, y), _z(z) { }
Point3d(const Point3d &rhs)
: Point(rhs), _z(rhs._z){ }
~Point3d();
Point3d& operator=(const Point3d&);

virtual float z() { return _z; }

protected:
float _z;
};
傳統的“constructor擴充現象”並沒有用,這是因為virtual base class的“共享性”之故:

//不合法的constructor擴充內容
Point3d* Point3d::Point3d(Point3d *this, float x, float y, float z)
{
this->Point::Point(x, y);
this->_vptr_Point3d = _vtbl_Point3d;
this->_vptr_Point3d_Point = _vtbl_Point3d_Point;
this->_z = rhs._z;
return this;
}
試想下面三種類派生情況:

class Vertex : virtual public Point{ … }
class Vertex3d : public Point3d, public Vertex{ … }
class PVertex : public Vertex3d { … }

Vertex的constructor必須呼叫Point的constructor。然而當Point3d和Vertex同為Vertetx3d的subobjects時,它們對Point constructor的呼叫操作一定不可以發生,取而代之的是,作為一個最底層的class,Vertex3d有責任將Point初始化,而更往後(往下)繼承,則由PVertex(不再是Vertex3d)來負責完成“被共享之Point subobject”的構造。

constructor的函式本身因而必須條件式地測試傳進來的引數,然後決定呼叫或不呼叫相關的virtual base class constructors,下面就是Point3d的constructor擴充內容:

//在virtual base class情況下的constructor擴充內容
Point3d* Point3d::Point3d(Point3d* this, bool _most_derived,
float x, float y, float z){

if(_most_derived != false)
    this->Point::Point(x, y);
    
this->_vptr_Point3d = _vtbl_Point3d;
this->vptr_Point3d_Point = _vpbl_Point3d_Point;
this->_z = rhs._z;
return this;

}
在更深層次的繼承情況下,例如Vertex3d,當呼叫Point3d和Vertex的constructor時,總是會把_most_derived引數設為flase。於是就壓制了兩個constructors中對Point constructor的呼叫操作:

//在virtual base class情況下constructor擴充內容
Vertex3d* Vertex3d::Vertex3d(Vertex3d *this, bool _most_derived,
float x, float y, float z){
if(_most_derived != false)
this->Point::Point(x, y);

//呼叫上一層base classes
//設定_most_derived為false
this->Point3d::Point3d(false, x, y, z);
this->Vertex::Vertex(false, x, y);

//設定vptrs
//安插user code
return this;

}
這樣的策略得以保證語意的正確無誤。如:當我們定義

Point3d origin;
時,Point3d constructor可以正確呼叫其Point virtual base class subobject。而當我們定義:

Vertex3d cv;
時,Vertex3d constructor正確呼叫Point constructor。Point3d和Vertex的constructors會做每一件該做的事情——對Point的呼叫操作除外。

“virtual base class constructors的被呼叫”有著明確的定義:只有當一個完整的class object被定義出來時,它才會被呼叫;如果object只是某個完整object的subject,它就不會被呼叫

vptr初始化語意學(The Semantics of the vptr Initialization)
當我們定義一個PVertex object時,constructors的呼叫順序是:

Point(x, y);
Point(x, y, z);
Vertex(x, y, z);
Vertex3d(x, y, z);
PVertex(x, y, z);
假設這個繼承體系中的每一個class都定義了一個virtual function size(),該函式賦值傳回class的大小。我們寫:

PVertex pv;
Point3d p3d;

Point *pt = &pv;
那麼這個呼叫操作:

pt->size();
將傳回PVertex的大小,而:

pt = &p3d;
pt->size();
將傳回Point3d的大小。

C++ 語言規則告訴我們,在Point3d constructor中呼叫的size()函式,必須被決議為Point3d::size()而不是PVertex::size()。更一般地,在一個class(本例為Point3d)的constructor(和destructor)中,經由構造中的物件(本例為PVertex)來呼叫一個virtual function,其函式例項應該是在此class(本例為Point3d)中有作用的那個。由於各個constructors的呼叫順序,上述情況是必要的。

Constructors的呼叫順序是:由根源而末端(bottom up)、由內而外(inside out)。當base class constructor執行時,derived例項還沒有被構造起來。在PVertex constructor執行完畢之前,PVertex並不是一個完整的物件:Point3d constructor執行之後,只有Point3d subobject構造完畢。

如果呼叫操作限制必須在constructor(或destructor)中直接呼叫,那麼答案十分明顯:將每一個呼叫操作以靜態方式決議之,千萬不要用到虛擬機制。

vptr 初始化操作應該如何處理? vptr初始化操作在base class constructors呼叫操作之後,但是在程式設計師供應的程式碼或是“memeber initialization list中所列的members初始化操作”之前。

令每一個base class constructor設定其物件的vptr,使它指向相關的virtual table之後,構造中的物件就可以嚴格而正確地變成“構造過程所幻化出來的每一個class”的物件。也就是說,一個PVertex物件會先形成一個Point物件、一個Point3d物件、一個Vertex物件、一個Vertex3d物件,然後才成為一個PVeretex物件。在每一個base class constructors中,物件可以與constructors’s class 的完整物件作比較。對於物件而言,“個體發生學”概況了“系統發生學”。constructor的執行演算法通常如下:

在derived class constructor中,“所有virtual base classes”及“上一層base class”的constructors會被呼叫
上述完成之後,物件的vptrs被初始化,指向相關的virtual tables
如果有member initialization list的話,將在constructor體內擴充套件開來。這必須在vptr被設定之後才做,以免有一個virtual member function被呼叫。
最後,執行程式設計師所提供的程式碼
例如:已知下面這個由程式設計師定義的PVertex constructor:

PVertex::PVertex(float x, float y, float z)
_next(0), Vertex3d(x, y, z), Point(x, y)
{
if(spyOn){
cerr << “Within PVertex::PVertex()”
<< "size: " << size() << endl;
}
}
它可能被擴充套件為:

//PVertex constructor的擴充套件結果
PVertex* PVertex::PVertex(PVertex *this, bool _most_derived,
float x, float y, float z){
//條件式呼叫virtual base constructor
if(_most_derived != false)
this->Point::Point(x, y);

//無條件地呼叫上一層base
this->Vertex3d::Vertex3d(x, y, z);

//將相關的vptr初始化
this->_vptr_PVertex = _vtbl_PVertex;
this->_vptr_Point_PVertex = _vtbl_Point_PVertex;

//程式設計師縮寫程式碼
if(spyOn){
    cerr << "Within PVertex::PVertex()"
            Point3d::Point3d(),
         << "size: " 
         << (*this->_vptr_PVertex[3].faddr)(this) 
         << endl;
}

//傳回被構造的物件
return this;

}
下面是vptr必須被設定的兩種情況:

當一個完整的物件被構造起來時,如果我們宣告一個Point物件,Point constructor必須設定其vptr。
當一個subobject constructor呼叫了一個virtual function(不管是直接呼叫還是間接呼叫時)。
如果我們宣告一個PVertex物件,然後由於我們對其base class constructors的最新定義,其vptr將不再需要在每一個base class constructors中被設定。解決之道是把constructor分裂為一個完整的object實體和一個subobject實體。在subobject實體中,vptr的設定可以省略(如果可以的話)。

物件複製語意學(Object Copy Semantics)
一個class對於預設的copy assignment operator,在以下情況,不會表現出bitwise copy語意:

當class內含一個member object,而其class有一個copy assignment operator時
當一個class的base class有一個copy assignment operator時
當一個class聲明瞭任何virtual functions(我們一定不要拷貝右端class object的vptr地址,因為它可能是一個derived class object)時
當class繼承自一個virtual base class(不論base class有沒有copy operator)時
C++ Standard上說copy assignment operators並不表示bitwist copy semantics是nontrival。實際上,只有nontrivial instances才會被合成出來

對於Point class定義如下:

class Point{
public:
Point(float x = 0.0, float y = 0.0);
//… 沒有virtual function
protected:
float _x, _y;
};
當有如下賦值(assign)操作:

Point a, b;
a = b;
由bitwise copy完成,把Point b拷貝到Point a,其間並沒有copy assignment operator被呼叫。從語意或效率上考慮,這都是我們所需要的,注意,我們還是可能提供一個copy constructor,為的是把name return vale(NRV)優化開啟,copy constructor的出現不應該讓我們也一定要提供一個copy assignment operator。

現在我要匯入一個copy assignment operator,用以說明該opeartor在繼承之下的行為:

inline Point& Point::operator=(const Point& p){
_x = p._x;
_y = p._y;
return *this;
}
現在派生一個Point3d class,(請注意是虛擬繼承)

class Point3d : virtual public Point{
public:
Point3d(float x = 0.0, float y = 0.0, float z = 0.0);
//…
protected:
float _z;
};
如果我們沒有為Point3d定義一個copy assignment opeartor,編譯器就必須合成一個(因為前述的第二項和第四項理由),合成而得的東西可能看起來像這樣:

//被合成的copy assignment operator
inline Point3d& Point3d::operator=(Point3d* const this, const Point3d &p){
//呼叫base class的函式實體
this->Point::operator=§;

//memberwise copy the derived class members
_z = p._z;
return *this;

}
下面是個Vertex copy operator,其中Vertex也是虛擬繼承自Point:

//class Vertex : virtual public Point
inline Vertex& Vertex::operator=(const Vertex& v){
this->Point::operator=(v);
_next = v._next;
return *this;
}
這部分太難了,摸了半天沒摸清楚,等下次再啃吧。

析構語義學(Semantics of Destruction)
如果class沒有定義destructor,那麼只有在class內含的member object(抑或class自己的base class)擁有destructor的情況下,編譯器才會自動合成一個出來。否則,destructor被視為不需要,也就不需被合成。例如,我們的Point,預設情況下並沒有被編譯器合成出一個destructor——甚至雖然它擁有一個virtual function:

class Point{
public:
Point(float x = 0.0, float y = 0.0);
Point(const Point&);
virtual float z();
private:
float _x, _y;
};
類似的道理,如果我們把兩個Point物件組合成一個Line class:

class Line{
public:
Line(const Point&, const Point&);
//…
virtual draw();
//…
protected:
Point _begin, _end;
};
Line也不會擁有一個被合成出來的destructor,因為Point並沒有destructor。

為了覺得class是否需要一個程式層面的destructor(或是constructor),請你想想一個class object的生命在哪裡結束(或開始)?需要什麼樣的操作才能保證物件的完整?這是你寫程式時比較需要了解的(或是你的class使用者比較需要了解的)。這也是constructor和destructor什麼時候起作用的關鍵。舉個例子,已知:

{
Point pt;
Point *p = new Point3d;
foo(&pt, p);

delete p;
}
我們看到,pt和p在作為foo()函式的引數之前,都必須先初始化為某些座標值,這時候需要一個constructor,否則使用者必須明確的提供座標值。一般而言,class的使用者沒有辦法檢驗一個local變數和heap變數以知道它們是否被初始化。把constructor想象為程式的一個額外負擔是錯誤的,因為它們的工作有其必要性。如果沒有它們,抽象化(abstraction)的使用就會有錯誤的傾向。

一個由程式設計師定義的destructor被擴充套件的方式類似constructors被擴充套件的方式,但順序相反:

destructor的函式本體現在被執行,也就是說vptr會在程式設計師的程式碼執行前被重設(reset)
如果class擁有member class objects。而後者擁有destructors,那麼它們會以其宣告的順序的相反順序被呼叫
如果object內含一個vptr,那麼首先重設(reset)相關的virtual table
如果有任何直接的(上一層)nonvirtual base classes擁有destructors,它們會以其宣告順序的相反順序被呼叫
如果有任何virtual base classes擁有destructor,而目前討論的這個class是最尾端(most-derived)的class,那麼它們會以其原來的構造順序的相反順序被呼叫
就像constructor一樣,目前對於destructor的一種最佳實現策略就是維護兩份destructor實體:

一個complete object實體,總是設定好vptr(s),並呼叫virtual base class destructors
一個base class subobject實體;除非在destructor函式中呼叫一個virtual function,否則它絕不會呼叫virtual base class destructors並設定vptr。
一個object的生命結束於其destructor開始執行之時。由於每一個base class constructor都輪番被呼叫,所以derived object實際上變成了一個完整的object。例如一個PVertex物件歸還其記憶體空間之前,會依次變成一個Vertex3d物件、一個Vertex物件、一個Point3d物件,最後成為一個Point物件。當我們在destructor中呼叫member functiions時,物件的蛻變會因為vptr的重新設定(在每一個destructor中,在程式設計師所供應的碼執行之前)而受到影響。