GeekBand C++面向?qū)ο蟾呒壘幊蹋ㄏ? Second Week
本周主要是講述了C++的對象模型,通過對于對象模型的深入了解,我們可以知道虛函數(shù),多態(tài),this指針的了解。 以及部分 new,delete的部分詳細介紹。
Object Model
C++的對象模型是C++對象最重要的部分之一,且包含的內(nèi)容非常的豐富.C++中的class是由data member和function member組成的。對象模型的內(nèi)存布局和data member和function member緊密相關(guān)。這里主要總結(jié)下function member及相關(guān)繼承關(guān)系對與object model的影響。
C++支持三種類型的member function: static,nonstatic 和 virtual。
static member function
靜態(tài)成員函數(shù)有有以下的調(diào)用方式。
obj.func();
ptr->func();
Object::func(); // 這種member selection的寫法是一種寫法上的便利,它會被轉(zhuǎn)化成一個直接的函數(shù)調(diào)用
靜態(tài)成員函數(shù)本質(zhì)上和非成員函數(shù)是一樣的(編譯器會做一些 name mangled的處理)。如果取一個static member function的地址,獲得的將是其在內(nèi)存中的地址,其地址類型并不是一個“指向 class member function的指針”,而是一個“nonmember函數(shù)指針”。
靜態(tài)成員函數(shù)有以下特征:
1.它不能夠直接存取class中的nonstatic members
2.它不能夠聲明為const,volative,virtual
3.它不需要經(jīng)由class object才被調(diào)用
nonstatic member function
C++的設(shè)計準則之一就是:非靜態(tài)成員函數(shù)至少必須和一般的非成員函數(shù)有相同的效率。
編譯器在內(nèi)部會將非靜態(tài)成員函數(shù)通過 name mangling的處理成對等的非成員函數(shù)。通??赡苡幸韵聨讉€步驟:
1.改寫函數(shù)簽名, 添加一個額外的this指針的參數(shù)。
2.將每一個 “對nonstatic data member的存取操作” 改為竟有this指針來存取
3.將非靜態(tài)成員函數(shù)重新寫成一個外部函數(shù),該函數(shù)名經(jīng)過“name mangling” 處理,使它成為獨一無二的函數(shù)名。
virtual member function
C++中虛函數(shù)加上繼承是實現(xiàn)多態(tài)的手段。
一個類中如果有虛函數(shù),編譯器在類的構(gòu)造函數(shù)中會加入一個虛函數(shù)指針(vptr)的初始化。vptr指向一個虛函數(shù)表(vtbl). vtbl中存放著相應(yīng)虛函數(shù)的對應(yīng)的函數(shù)地址。
class Point {
public:
virtual ~Point();
virtual float x() const;
private:
float x_;
};
class Point2d : public Point{
public:
virtual float y() const;
float length() const;
private:
float x_, y_;
};
類的內(nèi)存布局如下:
Point* ptr = new Point();
ptr->x();
這個會被轉(zhuǎn)化為
(ptr->vptr[2])(ptr)
所以Point*類型的變量,會根據(jù)指向不同的類(基類或子類)的實例,根據(jù)vptr指向的對應(yīng)slots的函數(shù)地址,執(zhí)行不同的函數(shù),從而實現(xiàn)多態(tài)。
multi inheritance
多重繼承的情況要比單繼承要復雜。
class base1 {
public:
virtual base1();
};
class base2 {
public:
virtual ~base2();
};
class devired : public base1, public base2 {
};
相關(guān)類的內(nèi)存布局如下:
從內(nèi)存的布局可以看出,子類分別繼承了基類的虛函數(shù)表,但是也不是完全的獨立繼承。繼承的兩個虛函數(shù)表有不同的地位。一般按照繼承的順序,第一個(base1)基類中的虛函數(shù)和第二個基類(base2)中的相關(guān)虛函數(shù)。
virtual inheritance
虛繼承
為了解決從不同途徑繼承來的同名的數(shù)據(jù)成員在內(nèi)存中有不同的拷貝造成數(shù)據(jù)不一致問題,將共同基類設(shè)置為虛基類。這時從不同的路徑繼承過來的同名數(shù)據(jù)成員在內(nèi)存中就只有一個拷貝,同一個函數(shù)名也只有一個映射。這樣不僅就解決了二義性問題,也節(jié)省了內(nèi)存,避免了數(shù)據(jù)不一致的問題。
class 派生類名:virtual 繼承方式 基類名
virtual是關(guān)鍵字,聲明該基類為派生類的虛基類。
在多繼承情況下,虛基類關(guān)鍵字的作用范圍和繼承方式關(guān)鍵字相同,只對緊跟其后的基類起作用。
聲明了虛基類之后,虛基類在進一步派生過程中始終和派生類一起,維護同一個基類子對象的拷貝
1.在多繼承情況下,虛基類關(guān)鍵字的作用范圍和繼承方式關(guān)鍵字相同,只對緊跟其后的基類起作用。
2.聲明了虛基類之后,虛基類在進一步派生過程中始終和派生類一起,維護同一個基類子對象的拷貝。
3.觀察類構(gòu)造函數(shù)的構(gòu)造順序,拷貝也只有一份。
new 與 delete
對于class,new 是先分配內(nèi)存,再調(diào)用ctor。delete是先調(diào)用dtor,然后再釋放內(nèi)存。
String* ps = new String("hello");
// 上面這句話轉(zhuǎn)化為下面的三句話
void* mem = operator new(sizeof(String));
ps = static_cast<String*>(mem);
ps->String::String("hello);
其中operator new 分為全局的和屬于某個類的區(qū)分??梢酝ㄟ^重載operator new(operator new[]) 或 operator delete(operator delete[]) 函數(shù)來自己控制內(nèi)存的管理。
重載 ::operator new 等
重載全局的 operator new 等函數(shù)是一件非常危險的事情,它會影響到所有的 new 等的內(nèi)存分配。
void* myAlloc(size_t size)
{ return malloc(size); }
void myFree(void* ptr)
{ return free(ptr); }
// 全局的函數(shù)不可以聲明于任何一個 namespace 內(nèi)
inline void* operator new(size_t size)
{ cout << "my global new" << endl; return myAlloc(size); }
inline void* operator new[](size_t size)
{ cout << "my global new[]" << endl; return myALloc(size); }
// delete 同理
重載 member operator new/delete
對于某個類重載其operator new/delete的函數(shù),可以只針對這個類使用自定義的內(nèi)存管理。
class Foo {
public:
void* operator new(size_t);
void operator delete(void*, size_t);
// ...
};
在使用new或delete時可以指定使用全局的 operator new 或operator delete 函數(shù)
// 如果沒有定義member的operator new的方法,就調(diào)用global的方法,有就調(diào)用自定義的member的版本
Foo* pf = new Foo;
// 強制使用 global的版本
Foo* pf = ::new Foo;
重載 new(), delete()
我們可以重載 class member operator(),寫出多個版本,前提是每一個版本都必須有特殊的參數(shù)列,其中第一個參數(shù)必須是size_t,其余參數(shù)以 new 所制定的placement argument為初值。出現(xiàn)于 new(...)小括號內(nèi)的便是所謂的 placement arguments。這種寫法被稱為placement new。
我們也可以重載class member operator delete(),寫出多個版本。但是他們絕不會被delete調(diào)用。只有當 new 所調(diào)用的 ctor 拋出 exception, 才會調(diào)用這些重載版的 operator delete()。它只可能這樣被調(diào)用,主要用來歸還未能完全創(chuàng)建成功的object所占用的memory。
const
const是一個非常好的關(guān)鍵字,如果可以用,就需要加上。
const可以用來修飾函數(shù)(只限成員函數(shù))和 函數(shù)的參數(shù)。修飾成員函數(shù)表示此函數(shù)保證不更改data members。
關(guān)于const object 和 non-const object 調(diào)用 const 和 non-const 函數(shù)的關(guān)系可以用以下的表格來表示。
| const object | non-const object | |
|---|---|---|
| const member function | ? | ? |
| non-const member function | ? | ? |
同時const也是屬于成員函數(shù)的簽名的一部分,一個類中可以同時出現(xiàn)nonconst 和const兩種版本。
當成員函數(shù)的const和non-const版本同時存在時,const object只能調(diào)用 const的版本,non-const object 只能調(diào)用non-const 的版本。