抽象基類
? 現有如下代碼:
class Abstract_base
{
public:
virtual ~Abstract_base() = 0;
virtual void interface() const = 0;
virtual const char* mumble() const { return _mumble; }
protected:
char* _mumble;
}
? 以上抽象基類宣告有幾個問題:
- 即使class被宣告為抽象基類,其依然需要explicit constructor來初始化protected data member _mumble,否則derived class無法決定_mumble初值
- 抽象基類的virtual destructor不要宣告為pure,因為每個derived class destructor會被編譯器擴張,以靜態方式呼叫每個virtual base class和上一層base class的destructor
- mumble()不應宣告為virtual function,因為其定義的內容和型別無關,derived class并不會改寫此函式
//合理的宣告
class Abstract_base
{
public:
virtual ~Abstract_base();
virtual void interface() = 0;
const char* mumble() const { return _mumble; }
protected:
Abstract_base( char* pc = 0 );
char* _mumble;
}
- 一般而言,class的data member應被初始化,且只在constructor中或在class的其他member function中指定初值,其他操作都會破壞封裝性質,讓class的維護和修改變得愈加困難
- 我們可以靜態地定義和呼叫一個pure virtual function,但不能經由虛擬機制
- c++保證繼承體系中每個class object的destructors都能被呼叫,編譯期不可壓抑這一操作,且編譯器并沒有足夠知識合成pure virtual destructor函式定義
- 虛擬基類中,不要把所有的member functions都宣告為virtual function,再靠編譯器的優化把非必要的虛擬呼叫去除
- 虛擬基類中,virtual function不使用const
物件構造
不繼承
? 現有如下片段:
typedef struct
{
float x, y, z;
}Point;
Point global;
Point foobar()
{
Point local;
Point* heap = new Point;
*heap = local;
delete heap;
return local;
}
-
對于Point這樣的宣告,在c++會被貼上Plain OI' Data標簽,編譯器并不會為其宣告default constrcutor、destructor、copy constructor、copy assignment operator
-
對于 Point global; 這樣的定義,在c++中members并沒有被定義或呼叫,行為和c如出一轍,編譯器并不會呼叫constructor和destructor,除非在c中,global被視作臨時性定義
臨時性定義:因為沒有顯示初始化操作,一個臨時性定義可以在程式多次發生,但編譯器最侄訓將這些實體鏈接折疊起來,只留下一個實體,放在data segment中"保留給未初始化的global object使用的"空間 但在c++中并不支持臨時性定義,對于此例,會阻止后續的定義 -
c++中所有全域物件都被以初始化過的資料對待
-
對于 Point* heap = new Point;編譯器并不會呼叫default constructor,只是 Point* heap = __new( sizeof(Point) ),delete亦是如此
-
對于*heap = local;編譯器并不會呼叫copy assignment operator做拷貝,但只是像c那樣做簡單的bitwise
-
return操作也是,只是簡單的bitwise,并沒有呼叫copy constructor
? 現有如下片段:
class Point
{
public:
Point( float x = 0.0, float y = 0.0, float z = 0.0 ) : _x(x), _y(y), _z(z) { }
//沒有copy constructor,copy operator,destructor
private:
float _x, _y, _z;
}
void do()
{
Point local1 = {1.0, 1.0, 1.0};
}
-
對于將class中成員設定常量值,使用explicit initialization list更有效率,因為當函式的活動記錄(activation record)被放進堆疊,initialization list的常量即可放入local1記憶體中
活動記錄程序的呼叫是程序的一次活動,當程序陳述句(及其呼叫)結束后,活動生命周期結束,變數的生命周期為其從被定義后有效存在的時間 -
但explicit initialization list也有不足:
- class member需要為public
- 只能指定常量,因為其常量在編譯器即可求值
- 編譯器并沒有自動施行它,初始化很可能失敗
-
若呼叫之前的例子,放在現在編譯器也不會呼叫destructor,因為并沒有顯示地提供destructor
delete heap;
? 現有如下片段:
class Point
{
public:
Point( float x = 0.0, float y = 0.0, float z = 0.0 ) : _x(x), _y(y), _z(z) { }
virtual float z();
//沒有copy constructor,copy operator,destructor
private:
float _x, _y;
}
-
匯入了virtual functions會引發編譯器對class Point的膨脹:
- 定義的constructor附加一些代碼,以便將vptr初始化,這些代碼附加在任何base class constructors呼叫后,user code之前
- 合成一個copy constructor 和 一個assignment operator,且不再是有用的(trivial),但implicit desctructor仍為有用的
-
這種情況下,編譯器在優化狀態下可能會把object的連續內容拷貝到另一個object上,且不是memberwise,因此,編譯器會盡量延遲nontrivial members的合成操作,直到遇到合適場合
-
例如之前的例子,此時就很有可能合成copy assignment operator,以及inline expansion,對于return,又因為合成copy constructor,函式內部又需改寫;但若編譯器支持NRV,內部會改寫為constructor,此時將不用呼叫copy constructor
*heap = local; return local;
繼承
T object;
對于以上定義,編譯器會擴充每一個constructor,一般而言擴充如下:
- 所有virtual base class constructors必須被呼叫,從左到右,從最深到最淺
- 若class被列于member initialization list,如果有任何顯示指定的引數,都應傳過去,若沒有列于list,而class有default constructor,則呼叫此
- 此外,class中的每個virtual base class subobject的offset必須在執行期可被存取
- 若 class object 是最底層的class,其constructors可能被呼叫
- 所有上層的base class constructors必須被呼叫,以base class的宣告順序
- 若class被列于member initialization list,如果有任何顯示指定的引數,都應傳過去,若沒有列于list,而class有default constructor,則呼叫此
- 若base class是多重繼承下的第二或后繼base class,那么this指標需調整
- 若class object有virtual table pointers,其需指定初值
- 記錄在member initialization list的data member初始化會被放進constructor函式本體,以members宣告順序為順序,若有一個member沒有出現在member initialization list,但其有default constructor,那么該default constructor必須被呼叫
? 實體:
class Point
{
public:
Point( float x = 0.0, float y = 0.0 );
Point( const Point& );
Point& operator=( const Point& );
virtual ~Point();
virtual float z() { return 0.0; }
protected:
float _x, _y;
};
class Line
{
Point _begin, _end;
public:
Line( float = 0.0, float = 0.0, float = 0.0, float = 0.0 );
Line( const Point&, const Point& ) : _end(end), _begin(begin);
draw();
...
};
//擴充
Line* Line::Line( Line* this, const Point&, const Point& )
{
this->begin.Point::Point( begin );
this->end.Point::Point( end );
return this;
}
//合成隱式的Line destructor,若Line派生自Point,合成的將會是virtual
Line a;
- 對于虛擬繼承,以上constructor擴張方式將不再支持,這是因為virtual base class的共享性,否則會導致多次virtual base class的constructor,針對虛擬繼承,每個derived class constructor擴張方式需發生改變,添加 bool __most_derived來判斷是否為派生最底層,若為最底層則呼叫virtual base class consturctor
vptr初始化
? 現有如下片段:
//假設每個class都定義了virtual function size(),傳回class大小,每個constrcutor中呼叫size()
Point(x,y);
Point3d(x,y,z);
Vertex(x,y,z);
Vertex3d(x,y,z);
PVertex(x,y,z);
//當我們定義PVertex object,前五個constructor各自呼叫自己的size()
- 做到如上機制,我們需要在執行一個constructor時,必須限制一組virtual functions候選名單,通過控制vptr的初始化和設定
- vptr的初始化應在base class constructors呼叫后,在member initialization list所列member初始化操作前
? 此時,constructor也需改變:
- 在derived class constructor中,呼叫所有virtual base classes及上一層base class 的constructors
- 然后,初始化vptr,指向相關virtual table
- 若有member initialization list,將在constructor展開,但必須在vptr被設定后才施行
- 最后,執行user code
//改變后的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 class
this->Vertex3d::Vertex3d(x,y,z);
//初始化vptr
this->__vptr_PVertex = __vtbl_PVertex;
this->__vptr_Point__PVertex = __vtbl_Point__PVertex;
//size()
...
return this;
}
? 當然以上方案并不完美:
Point::Point( float x, float y ) : _x(x), _y(y) { }
Point3d::Point3d( float x, float y, float z ) : Point(x,y), _z(z) { }
? 此時,若宣告PVertex,由于對其base class constructor的最新定義,其vptr將不再需要在每個base class constructor中被設定,因此,我們需要把constructor分裂為一個完整的object實體和sunobject實體
- 在class的constructor的member initialization list中呼叫該class 的virtual functions,在語意上可能不安全,因為函式本身可能得依賴未被設立初值的members
物件復制
? 設計一個class,并以一個class object指定給另一個class object,我們有三種選擇:
- 什么都不做,實施默認行為
- 提供一個explicit copy assignment operator
- 顯示拒絕把class object指定給另一個class object,也就是將copy assignment operator宣告為private,且不提供定義
- 只有在默認的member wise copy行為不安全或不正確時,才需要設計一個copy assignment operator,且如果class有bitwise copy,隱式的assignment operator不會合成
? class對于default 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
-
copy assignment operators并不表示bitwise copy是nontrivial,只有nontrivial instances才被合成
-
即使賦值由bitwise copy完成,并沒有呼叫copy assignment operator,但還是需要提供一個copy constructor,以此打開NRV優化
-
盡可能不要允許一個virtual base class的拷貝操作,不要在任何virtual base class中宣告資料
物件效能
-
對于單一繼承和多重繼承,若class使用bitwise copy,一般不會合成copy constructor,就不會增加效率成本
-
對于虛擬繼承,bitwise copy不再支持,而是合成copy assignment operator和inline copy constructor,導致成本大大增加,且繼承體系復雜度增加,物件拷貝和構造的成本也會增加
物件析構
- 若class沒定義destructor,只有在class內含member object含有destructor時,編譯器才會合成destructor
- 若base class不含desturctor,那么derived class也不需要desturctor
? destructor被擴展的方式,與constructor相似,但順序相反:
- destructor函式本體先被執行
- 若class含有member class object,而后者含有destructors,他們會以其宣告順序的相反順序被呼叫
- 若object內含vptr,現需被重新指定,指向適當的base class的virtual table
- 若有任何直接的nonvirtual base classes含有destructor,它們會以其相反的宣告順序被呼叫
- 若由任何virtual base classes含有destructor,如之前的PVertex例子,會以其原來的構造順序的相反順序呼叫
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/519029.html
標籤:其他
下一篇:使用 Windows Core Audio APIs 進行 Loopback Recording 并生成 WAV 檔案
