Boolan/C++面向?qū)ο蟾呒壘幊?part5

C++面向?qū)ο蟾呒壘幊?part5

2017-11-14 11:59:35 / herohlc


item1. 對象模型:vptr與vtbl

1. 類對象內(nèi)存模型

class A{
public:
   virtual void vfunc1();
   virtual void vfunc2();
   void func1();
   void func2();

private:
    int m_data1;
    int m_data2;
};

class B :public A{
public:
   virtual void vfunc1();

   void func2();

private:
    int m_data3;
};

class C :public B{
public:
   virtual void vfunc1();

   void func2();

private:
    int m_data1;
    int m_data4;  
};
1511072519130.png
  1. 注意圖中的類對象模型,類對象中僅包含成員數(shù)據(jù)/vptr,不包括函數(shù)地址。
  2. deriverd類數(shù)據(jù)成員可以與base類數(shù)據(jù)成員重名,兩者保存在不同的區(qū)域。
  3. base類有虛函數(shù),base類就會有vptr,base類對象有vptr則子類一定有vptr。

擴展:dervied class 調(diào)用base class 函數(shù)/函數(shù)覆蓋

#include <iostream>
using std::cout;
using std::endl;

class A{
public:
   virtual void vfunc1(){};
   virtual void vfunc2(){};
   void func1(){ cout << "A::func1" << endl;};
   void func2(){ cout << "A::func2" << endl;};

private:
    int m_data1;
    int m_data2;
};

class B :public A{
public:
   virtual void vfunc1(){};

   void func2() {
       cout << "B::func2" << endl;
   };

private:
    int m_data3;
};

class C :public B{
public:
   virtual void vfunc1(){};

   void func2(){ cout << "C::func2" << endl;};

private:
    int m_data1;
    int m_data4;
};
int main() {
    A a;
    B b;
    C c;
    b.func1();
    b.func2();
    c.func1();
    c.func2();

    return 0;
}

A::func1
B::func2
A::func1
C::func2
  1. derived 類繼承了base類的函數(shù)調(diào)用權(quán),所以可以通過derived對象調(diào)用base類的接口。
  2. derived 類的與base類的同名接口,derived會覆蓋base類。

注意??:C++繼承都繼承了哪些東西

  1. 數(shù)據(jù)
  2. 函數(shù)調(diào)用權(quán)。不是繼承函數(shù)相關(guān)的內(nèi)存

2. vptr vs. vtbl

base類有虛函數(shù),base類就會有vptr,base類對象有vptr則derived類一定有vptr。

虛函數(shù)的繼承方式

  1. dervied 類如果不override base類的虛函數(shù),則直接繼承base類的虛函數(shù)
  2. derived 類如果override base類的虛函數(shù),則在虛表中替換base類的虛函數(shù)地址
  3. 多重繼承中,虛函數(shù)不會“跳著”間接繼承,而是繼承自己的base類的虛函數(shù)。上圖中c直接繼承b的虛函數(shù),而不是直接繼承自a的虛函數(shù)。

繼承關(guān)系下生成的函數(shù)

關(guān)注上圖中的a/b/c 三個類中生成的所有的函數(shù),分成虛函數(shù),非虛函數(shù)兩種類型。

vtbl

3. 靜態(tài)綁定 vs. 動態(tài)綁定

靜態(tài)綁定的函數(shù)調(diào)用方式

函數(shù)調(diào)用時,執(zhí)行call xxx(地址)。

動態(tài)綁定的函數(shù)調(diào)用方式

通過指針找到對象的vtbl,然后找到正確的函數(shù)地址。
解釋成代碼形式如下:
(*(p->vptr)[n])(p)

(*(p->vptr)[n])(p)中的n與虛函數(shù)的聲明順序一致

4. 多態(tài)的示例

1511075056197.png

多態(tài)解決的問題

1. 如何用一個只能容納一種元素類型的容器,存儲不同類型的元素?

容器保存的元素類型為,具有繼承關(guān)系的base類指針,其指向?qū)ο鬄閐ervied類對象。

2. 如何讓容器中的元素(base類指針),調(diào)用相同的接口卻具有不同的行為?

接口為虛函數(shù)。

多態(tài)的條件= up-cast pointer+ virtual function


item2. 對象模型:this

1. this指針參與到多態(tài)中

1511075265450.png
  1. 利用多態(tài)機制,在base類的成員函數(shù)流程中實現(xiàn)通用的邏輯,base類提供通用的接口,通用接口的實現(xiàn)可以遲后由derived 類實現(xiàn)。
  2. 成員函數(shù)通常是通過對象調(diào)用(static成員函數(shù)除外),所以在調(diào)用函數(shù)時編譯器會知道哪個對象在調(diào)用函數(shù),此時當(dāng)前對象的指針會傳遞給該成員this指針。

注意??:

  1. 編譯器在執(zhí)行虛函數(shù)的調(diào)用(通過指針的方式)時,是通過指針指向內(nèi)存單元的vptr找到vtbl中的函數(shù)地址,最后去調(diào)用地址中的函數(shù)。從實際的底層實現(xiàn)機制更容易解釋多態(tài)語法。

item3. 對象模型:dynamic binding

1. 從匯編的角度解釋dynamic binding

1511075345112.png
  1. 上圖中a.vfunc1()的調(diào)用為static binding。
  2. static binding的匯編執(zhí)行形式:call xxxx
1511075435047.png
  1. dynamic binding 匯編執(zhí)行等價于 c的形式(*(p->vptr)[n])(p))

注意??
用對象(非指針)的方式調(diào)用成員函數(shù),不會造成多態(tài)。


item4. const

1. const member function

注意??
const member function中const修飾成員函數(shù)的形式只能用在成員函數(shù)中,不能用在全局的函數(shù)中。理解:這里的const是用來修飾this指針的,全局函數(shù)沒有this指針。

2. const obj vs. non-const obj vs. const member function vs. no-const member function

const member function中的const的作用,承諾該成員函數(shù)不會修改對象內(nèi)容。本質(zhì)是將this指針聲明為const*const形式。

xx const object non-const object
const member function v v
non-const member function x v

在設(shè)計類接口時要考慮const

class String{
public:
    print(){}  // bad , non-const print  
}
const String str("hello");
str.print();  // error, 

建議:如果member function 不改變對象數(shù)據(jù),應(yīng)該將該member function 聲明為const。否則const object 無法調(diào)用該接口。
在設(shè)計類的接口時就要確定要不要加const。

引申:函數(shù)(全局/class member function)的形參,如果不想改變實參,要將其聲明為const &

成員函數(shù)的const 和non-const 版本共存時,調(diào)用誰?

class string{
charT operator[](size_type pos)const
{/*不考慮copy on write*/}

reference operator[](size_type pos) 
{/* 必須考慮copy on write*/}
}
}

string a;
cout << a[1];  // 調(diào)用 non-const operator[] 
                 // non-const operator[] ?
a[1] = 'a';    // 調(diào)用 non-const operator[] 
                 // non-const operator[] ?
  1. const 作為函數(shù)簽名的成分
  2. reference 返回值類型的函數(shù)可以作為左值。 因此上面代碼中non-const版本中的operator[] 可以作為左值使用,需要考慮copy on write,但const 版本的operator[] 返回值類型為charT 智能是右值不必考慮copy on write。
  3. 函數(shù)設(shè)計要考慮是否會把函數(shù)作為左值使用。

注意??
當(dāng)成員函數(shù)的const和non-const版本同時存在,const object只會調(diào)用const版本,non-const object 只會調(diào)用non-const版本。


item5. new & delete

1. new /delete 表達式 vs. operator new /delete

  1. new /delete表達式 實現(xiàn)中會分解為多個步驟,其中包括調(diào)用operator new/delete。

注意??:

  1. new /delete 表達式 不能被重載,operator new /delete 可以被重載。
  2. operator new /delete 是對內(nèi)存的操作.operator delete不包含調(diào)用析構(gòu)函數(shù)的動作,析構(gòu)函數(shù)在delete 表達式中調(diào)用。

item6. 重載operatro new /delete

1. 重載全局operator new/delete

inline void* operator new(size_t size){
    cout << "my new  :" <<size<< endl;
    return malloc(size);
}

inline  void* operator new[](size_t size) {
    cout << "my new [] : " << size<< endl;
    return malloc(size);
}

inline void operator delete(void* ptr){
    cout << "my delete" << endl;
    free(ptr);
}

inline void operator delete[](void * ptr) {
    cout << "my delete[]" << endl;
    free(ptr);
}
  1. operator new 需要一個size參數(shù)
  2. operator new 不是由用戶調(diào)用,由編譯器在expression new中調(diào)用
  3. 注意返回值類型為void*
  4. operator new 只需指定 size
  5. operator delete需要指定地址和可選的size

2. 重載member operator new/delete

class Foo {
public:
    void*operator new(size_t size) {
        cout << "Foo new" << endl;
        return malloc(size);
    }
    void operator delete(void* ptr){
        cout << "Foo delete" << endl;
        free(ptr);
    }
};
int main() {

    Foo* f = new Foo;
    delete f;
    return 0;
}

Foo new
Foo delete

類如果重載operator new/delete ,在調(diào)用expression new 創(chuàng)建類對象時,expression new中將調(diào)用類的operator new。

expression new /delete分解過程

1511077397576.png

3. 重載member operator new[]/delete []

class Foo {
public:

    void*operator new[](size_t size) {
        cout << "Foo new [] : " << size <<endl;
        return malloc(size);
    }

    void operator delete[](void* ptr){
        cout << "Foo delete[] : " << ptr << endl;
        free(ptr);
    }
};

expression new[] /delete[]分解過程

1511077834557.png
  1. 注意構(gòu)造和析構(gòu)的調(diào)用次數(shù),operator new中傳入的size值。

item7. 示例

class Foo {
public:
    void* operator new(size_t size) {
        cout << "Foo new" << endl;
        return malloc(size);
    }
    void operator delete(void* ptr){
        cout << "Foo delete" << endl;
        free(ptr);
    }

    void*operator new[](size_t size) {
        cout << "Foo new [] : " << size <<endl;
        return malloc(size);
    }

    void operator delete[](void* ptr, size_t size){
        cout << "Foo delete[] : " << ptr << endl;
        free(ptr);
    }

private:
    int _id;
    long _data;
    string _str;
};

1. 如何使用全局的operator new / delete

Foo * p = ::new Foo;
::delete p

2. operator new 傳入的size 大小

operator new[] 需要分配一個保存對象個數(shù)的內(nèi)存單元。


item8. 重載 new(), delete()

class member operator new() - placement new

1. class member placement new

示例
Foo* pf = new(300,'c')Foo

  1. class member 可以重載 operator new,寫出多個版本的operator new()
  2. 每個版本的聲明必須具有獨特的參數(shù)列,其中第一個參數(shù)必須是size_t,其余參數(shù)以new 所指定的placment arguments 為初值。

placement argument:new (…)小括號中的參數(shù)。size_t在聲明時默認(rèn)需要定義,調(diào)用處不用顯示指定,size_t 不是 使用時(…)中的參數(shù)。

2. class member operator delete () - placement delete

絕不會被 expression delete 調(diào)用,只有當(dāng)調(diào)用 placement new 之后調(diào)用的ctor拋出異常才會被調(diào)。

  1. class member operator delete () 不是必須定義的,如果定義要與placement new對應(yīng)。

3. 示例


class Foo {
public:
    Foo(){};
    Foo(int a){
        throw a;
    };

    void* operator new(size_t size, int extra){
        return malloc(size+extra);
    }

    void operator delete(void* ptr, int extra){
        cout << "placement delete called" << endl;
    }

private:
    int _id;
    long _data;
    string _str;
};
int main() {
    Foo* a = new(1)Foo;
    Foo* b = new(1)Foo(1);
    return 0;
}

item9. basic_string使用new(extra)擴充申請量

1511078434634.png

1. why using placement new

如果想在new對象時創(chuàng)建額外超過對象大小的內(nèi)存,使用placement new 代替 默認(rèn)的new。


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內(nèi)容