天天看點

Inside the C++ Object Model 深度探索對象模型 5-7

5構造,解構,拷貝語意學 Semantics of Construction, Destruction, Copy

純虛拟函數的存在 Presence of Pure VF

>pure virtual function可以被定義和調用invoke: 隻能被靜态調用statically, 不能經由虛拟機調用; Ex. inline void Abstract_base::interface() const {...} Abstract_base::interface();

>pure virtual destructor一定要定義, 每一個derived class destructor會被編譯器擴充, 以靜态方式調用每一個virtual base class以及上一層base class的destructor; 缺少base class destructor的定義會導緻鍊結失敗;

>繼承體系中的每一個class object的destructor都會被調用; 建議-不要把virtual destructor聲明為pure;

虛拟規格的存在 Virtual Specification

>一般而言把所有的成員函數都聲明為virtual function, 再靠編譯器的優化操作把非必要的virtual invocation去除, 不是好的設計觀念; Ex. inline函數的優化被破壞;

虛拟規格中const的存在

>決定一個virtual function是否const需要考慮到subclass的函數調用(const reference, const pointer), 以及derived instance修改data member的可能性;

重新考慮class的聲明

1

2

3

4

5

6

7

8

9

class

Abstract_base             

{             

public

:             

virtual

~Abstract_base() = 0;             

virtual

void

interface() 

const

= 0;             

virtual

const

char

* mumble() 

const

return

_mumble;}             

protected

:             

char

* _mumble;             

};

10

class

Abstract_base_update             

{             

public

:             

virtual

~Abstract_base_update(); 

// not pure virtual any more             

virtual

void

interface() = 0; 

// not const any more             

const

char

* mumble() 

const

return

_mumble;} 

// not virtual             

protected

:             

Abstract_base_update(

char

* pc = 0); 

// add a contructor with parameter             

char

* _mumble;             

};

5.1 無繼承情況下的對象構造

>在C中global被視為一個臨時性的定義, 放在data segment中的BBS空間, Block Started by Symbol; C++不支援臨時性的定義, 所有的全局對象被當作"初始化過的資料";

抽象資料類型

>Explict initialization list 比較高效 Ex. Point pt = {1.0, 1.0, 1.0}; 

三項缺點 1) class members必須為public; 2) 隻能指定常量, 因為它們在編譯時期就可以被評估求值evaluated; 3) 初始化行為的失敗可能性較高;

為繼承做準備

>virtual function的引入使每個Object擁有一個vtbl ptr, 這個指針提供了virtual接口的彈性, 代價是每一個object需要一個額外的word空間; 編譯器也會對class作出膨脹的轉化;

-Constructor增加vptr的初始化, 這些代碼在base class constructor之後, 在程式員代碼之前調用; Ex. this->__vptr_Point = __vtbl__Point;

-合成一個copyy constrctor和一個copy assignment operator.

>如果遇到函數需要以傳值by value的方式傳回一個local class object; Ex. T operator+(const T&, const T&) { T result; ... return result;} 提供一個copy constructor是比較合理的, 即使default memberwise語意已經足夠, 也能觸發NRV優化 name return value;

5.2 繼承體系下的對象構造

>Constructor可能内帶大量的隐藏代碼, 編譯器擴充每一個constructor, 擴充程度視class的繼承體系而定; 

1) 記錄在member initialization list中的data members初始化操作會被放進constructor, 以members的聲明順序為序; 2)如果有member沒有出現在member initialization list中, 但它有一個default constructor, 該default constructor必須被調用; 3) 在那之前, class object有virtual table points, 必須被設定初值; 4) 在那之前, 所有上層的base class constructor必須被調用, 以base class的聲明順序為序; 5) 在那之前, virtual base class constructors必須被調用, 左到右, 深到淺;

>member class objects的destructor調用次序和構造次序相反(子類->基類);

虛拟繼承 Virtual Inheritance

>由最底層most derived的class來負責完成被共享的base class的subobject的構造;

Vptr初始化語意學

>Constructors的調用順序是: 由根源到末端 bottom up, 由内到外 inside out;

5.3 對象複制語意學 Object Copy Semantic

>防止将一個calss object指定給另一個class object: 将copy assignment operator聲明為private, 定義為空(member function和class的friend可以調用private的operator, 當它們企圖影響拷貝時, 程式在連結時會失敗--和連結器有關, 不一定都失敗...)

>對于簡單的類結構(Ex. Point), 如果我們不提供copy assignment operator或copy constructor, 編譯器也不會産生出實體. 由于class有了bitwise copy語意, implicit copy被視為無用處的(trivial);

>class對于預設的copy assignment operator, 不會表現出bitwise copy的情況

1)當class内帶一個member object, 而其class有一個copy assignment operator; 2)當一個class的base class有一個copy assignment operator; 3)當一個class聲明了任何virtual functions(derived class object的vptr位址是相對的); 4) 當class繼承自一個virtual base class;

>C++ Standrand Section 12.8: 我們并沒有規定哪些代表virtual base class的subobject是否該被隐喻定義(implicitly defined)的copy assignment operator指派(指派 assign)内容一次以上;

>建議: 盡可能不要允許一個virtual base class的拷貝操作; 其他建議: 不要在任何virtual base class中聲明資料;

5.4 對象的功能 Object Efficiency

>bitwise copy擁有最好的效率, 一旦引入virtual繼承, class不再允許bitwise copy語意, 合成型的inline copy constructor和copy assignment operator大大降低效率;

5.5 解構語意學 Semantics of Destruction

>如果class沒有定義destructor, 那隻有在class内帶的member object或calss自己的base class擁有destructor的情況下, 編譯器才會自動合成, 否則destructor或被視為不需要;

>由程式員定義的destructor被擴充的方式類似constructor被擴充的方式, 順序相反 

1)如果object内帶一個vptr, 重設相關的vtbl; 2)destructor的函數本身被執行, vptr在程式員的代碼執行前被reset; 3)如果class擁有member objects, 而且擁有destructors, 它們會以聲明順序的反向順序被調用; 4)如果有任何直接的上一層/nonvirtual base class擁有destructor, 它們會以聲明順序的相反順序被調用; 5)如果有任何virtual base classes擁有destructor, 而且此class是最尾端most-derived的, 它們會以原來的構造順序反向被調用;

---Section5 End---

6執行期語意學 Runtime Semantics

6.1 對象的構造和解構

>一般而言會把object盡可能放置在被使用的那個程式段附近(在使用時才定義, 如果程式在之前傳回了, 可以避免産生對象), 這樣可以節省不必要的對象産生和銷毀的操作;  

全局對象 Global Objects

>靜态的初始化和記憶體釋放操作; 在main()用到global之前構造, 在main()結束之前銷毀;

>C++程式中所有的global objects都被放置在程式的data segment中. 如果指定值, object将以此作為初值, 否則object所配置到的内容為0;(C不會自動設定初值, C語言中global object隻能被常量表達式設定初值)

局部靜态對象 Local Static Object

>construct和destruct隻能被調用一次;

對象數組 Array of Objects

>1)配置記憶體給N個連續的Objects; 2)Constructor和Destructor施行與N個對象上;

void* vec_new(void* array, size_t elem_size, int elem_count, void (*constructor)(void*), void (*destructor)(void*, char))

void* vec_delete(void* array, size_t elem_size, int elem_count, void (*destructor)(void*, chhar))

Ex. Point knots[10]; vec_new(&knots, sizeof(Point), 10, &Point::Point, 0); //沒有定義destruction

Default Constructor和數組

>取得constructor的位址後, 它不能成為inline;

6.2 new和delete運算符

>int *pi = new int(5); 1)通過new運算符事體配置記憶體: int *pi = __new(sizeof(int)); 2)設立初值: *pi = 5;

>初始化操作應該在記憶體配置成功後才執行: int *pi; if (pi = __new(sizeof(int))) *pi = 5; delete運算符類似: if (pi != 0) __delete(pi);

>new運算符實際上是以标準的C malloc()完成, delete運算符也是以标準的C free()完成;

11

12

13

14

15

16

17

18

19

20

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;

}

extern

void

operator 

delete

(

void

*ptr)

{

if

(ptr)

free

((

char

*)ptr);

}

針對數組的new語意

>Point3d* p_array = new Point3d[10]; new時指定大小;

>delete時不用指定大小: delete [] p_array; 尋找數組次元給delete運算符的效率帶來很大影響, 當程式員們沒有提供中括号Ex. delete p_array; 隻有第一個元素會被析構, 其他的元素依然存在;

>删除指向derived class的base class指針: Point* ptr = new Point3d[10]; delete [] ptr;//隻會調用Point的析構;

for

(

int

ix = 0; ix < elem_count; ++ix)

{

Point3d* p = &((Point3d*)ptr)[ix];

delete

p;

}

Placement Operator new的語意

>Operator new的指針必須指向相同類型的class或者一塊新的記憶體, 足夠容納該類型的object; (derived class不符合)

char* arena = new char[sizeof(Point2w)]; OR Point2w* arena = new Point2w;

>一般而言Placement Operator new不支援多态polymorphism;

6.3 臨時性對象 Temporary Objects

>C++ Standard允許編譯器對臨時性對象的産生有完全的自由度: "在某些環境下由processor産生臨時性對象是有必要的, 也是比較友善的. 這樣也的臨時性對象由編譯器來定義." "臨時性對象的被銷毀, 是對完整表達式full-expression求值過程中的最後一個步驟. 該完整表達式造成臨時對象的産生."(C++Standard 12.2)

>凡含有表達式執行結果的臨時性對象, 應該存留到object的初始化操作完成為止;

const char *progNameVersion = progName + progVersion; --> String temp; operator+(temp, progName, progVersion); progNameVersion = temp.String::operator char*(); temp.String::~String(); //指向了未定義的heap記憶體;

const String &space = " "; --> String temp; temp.String::String(" "); const String &space = temp; //當temp被銷毀, reference也失效;

>"如果一個臨時性對象被綁定與一個reference, 對象将殘留, 知道被初始化的reference生命結束, 或知道臨時對象的生命範疇scope結束;"

臨時性對象的迷思

>反聚合 disaggregation (将臨時對象拆分, 放入緩存器)

---Section6 End---

7站在對象模型的尖端 On the Cusp of the Object Model

7.1 Template

>通用程式設計(STL)的基礎, 也用于屬性混合或互斥mutual exclusion機制(線程同步化控制)的參數化技術;

template<class Type>

Type min(const Type &t1, const Type &t2) {...} 用法: min(1.0, 2.0); //程式吧Type綁定為double并産生min()的程式文字實體(mangling名稱)

Template的具現行為 Template Instantiation

template

<

class

Type>

class

Point

{

public

:

enum

Status {unallocated, normailized, };  

Point(Type x = 0.0, Type y = 0.0, Type z = 0.0);

~Point();

void

*operator 

new

(

size_t

);

void

operator 

delete

(

void

*, 

size_t

);

private

:

static

Point<Type> *freeList;

static

int

chunkSize;

Type _x, _y, _z;

};

>編譯器對于template class的聲明不會有反應, 上述的static data members和enum還不可用;

>enum Status隻能通過template Point class的某個實體來存取或操作: Point<float>::Status s;//OK; Point::Status s;//ERROR;即使兩種類型抽象來說是一樣的; (将enum抽出刀一個nontemplate base class中避免多份拷貝)

>Point<float>::freeList; Point<double>::freeList; 兩個freeList實體; 分别與Point class的float instantiantion和double instantiation産生關聯;

>Point<float> *ptr = 0; 程式沒有行為發生; 指向class object的指針本身不是一個class object, 編譯器不需要知道與該class有關的members資料或object布局資料;

>const Point<float> &ref = 0; reference會真的具現出Point的float實體來; -->Point<float> temporary(float(0)); const Point<float> &ref = temporary; 0是整數, 會被轉化到Point<float>類型, 編譯器會判斷是否可行;

>const Point<float> origin; 會導緻template class的具現, float instantiation的對象布局會産生出來;

>Point<float> *p = new Point<float>; 具現 1)Point template的float執行個體; 2)new運算符(依賴size_t); 3)default constructor;

>兩種政策: 1)編譯時函數具現于origin和p存在的那個檔案中; 2)連結時, 編譯器被輔助工具重新激活, template函數實體存放在某個檔案(存儲位置)中;

Template的錯誤報告 Error Reporting within a Template

>一般編譯器對于一個template聲明, 在它被實際參數實作之前, 隻施行有限的錯誤檢查(文法); 文法之外的錯誤隻有在特定實體被定義後才會發現;

Template中的名稱決議方式 Name Resolution within a Template

>區分1)Scope of the template declaration 定義出template的程式; 2)Scope of the template instantiation 具現出template的程式;

extern

double

foo(

double

);

//1)

template

<

class

type>

class

ScopeRules

{

public

:

void

invariant() {

_member = foo(_val);

}

type type_dependent(){

return

foo(_member);

}

private

:

int

_val;

type _member;

};

extern

int

foo(

int

); 

//2)

ScopeRules<

int

> sr0;

>sr0.invariant(); 調用的是foo(double)-1), _val是int類型, 屬于"類型不會變動"的template class member, 函數的決議隻和函數的原型signature有關, 與傳回值無關; foo的調用和template參數無關, 屬于scope of the template declaration, 在此scope中隻有一個foo() -->1);

>sr0.type_dependent(); 類型相關, 與template參數有關, 參數将決定_member的具體類型; 屬于scope of the template instantiation; 在此scope下有兩個foo()聲明, 由于_member的類型是int, 調用的是foo(int)-2);

>編譯器必須保持兩個scope contexts: 1)scope of the template declaration, 專注于一般的template class; 2)scope of the template instantiant, 專注于特定的實體; 編譯器的決議resolution算法必須決定那個是适當的scope, 然後在其中搜尋适當的name;

Member Function的具現行為 Member Function Instantiation

>"如果一個virtual function被具現instantiated出來, 其具現點緊跟在其class的具現點之後"

7.2 異常處理 Exception Handling

>為了維持執行速度, 編譯器可以在編譯時期建立起用于支援的資料結構; 程式會膨脹, 編譯器可以忽略這些結構直到有exception被丢出;

>為了維護程式大小, 編譯器可以在執行時期建立起用于支援的資料結構; 影響了程式的速度, 編譯器隻有在必要的時候才建立那些資料結構;

Exception Handling 快速檢閱

>C++的exception handling組成: 1)一個throw子句, 在程式某處發出一個exception, 被丢出的exception可以是内建類型或使用者自定義類型; 2) 一個或多個catch子句, 每個catch子句都是一個exception handler. 處理某種類型的exception, 在大括号内處理程式; 3)一個try區段, 一系列的叙述句statements, 可能會引發catch子句起作用;

>推離程式unwinding the stack, 在函數被推離堆棧之前, 函數的local class objects的destructor會被調用;

對Execption Handling的支援

>對于一個exception, 編譯器負責: 1)檢驗發生throw操作的函數; 2)決定throw操作是否發生在try區段; 3)如果是, 編譯系統必須把exception type和每個catch子句比較; 4)如果比較吻合, 流程控制交到catch子句中; 5)如果throw發生不是在try區段中, 或沒有catch子句吻合, 系統必須 a)銷毀所有active local objects; b)從堆棧中将目前的函數unwind掉; c)進行到程式堆棧的下一個函數中去, 重複步驟 2)-5);

決定throw是否發生在try區段中

>-try區段外的區域, 沒有active local objects; -try區段外的區域, 有active local objects需要解構; -try區段以内的區域;

>program counter-range表格

将exception的類型和每個catch子句的類型比較

>類型描述器type descriptor;

當一個實際對象在程式執行時被抛出

>exception object會被産生并放在在相同形式的exceptio資料堆棧中;

>在一個catch子句中對exception object的任何改變都是局部性的.

7.3 執行期類型識别 Runtime Type Identification  

>downcast向下轉型有潛在的危險;

Type-Safe Downcast 保證安全的向下轉型操作

>支援type-safe downcast在空間和時間上的負擔 -需要額外的空間來存儲類型資訊type information, 通常是一個指針, 指向某個類型資訊節點; -需要額外的時間以決定執行期的類型runtime type;

Type-Safe Dynamic Cast 保證安全的動态轉型

>dynamic_cast; Ex. if(Derived* pD= dynamic_cast<Derived*>(pB)) ... else ...

References并不是Points

>對class指針類型施以dynamic_cast運算符, 會獲得true或false; -如果傳回真正的位址, 表示object的動态類型被确認; -如果傳回0, 表示沒有指向任何object;

>對reference施行dynamic_cast, 對于一個non-type-safe cast, 與指針的情況不同; 如果把一個reference設為0, 會引起一個臨時對象産生, 初值為0, reference被設定為該臨時對象的一個别名alias; -如果reference真正參考到适當的derived class, downcast會被執行; -如果reference不真正是某種derived class, 不能傳回0, 丢出一個bad_cast exception;

Ex. try { Derived &rD = dynamic_cast<Derived&>(rB); ...} catch {bad_cast} {... }

Typeid運算符

>typeid運算符傳回一個const reference, Ex. if (typeid(rD) == typeid(rB)) 類型為type_info; equal等号運算符是一個被overloaded的函數: bool type_info::operator==(const type_info&) const;

>type_info不隻是用于多态類polymorphic class, 也适用于内建類型, 使用者自定義類型(非多态); 這些情況下type_info object是靜态取得, 不是執行期取得;

7.4 效率和彈性

動态共享函數庫 Dynamic Shared Libraries

>目前的C++模型中, 新版lib的class object有變更, class的大小以及每個直接(或繼承的)members的偏移量offset都在編譯期就固定好(虛拟繼承的members除外).這樣提高效率, 但在二進制binary層面影響了彈性. object布局改變, 程式必須重新編譯; 沒有達到"library無侵略性";

共享記憶體 Shared Memory

>分布式distributed, 二進制層面的對象模型, 與程式語言無關;

---Section7 End---

使用者評論

暫無評論。

發表評論:
送出

繼續閱讀