細談C++多態性的”動”與”靜”

來源:互聯網
上載者:User

在我們討論多態的時候,先看看什麼是寫入程式碼和軟編碼:
寫入程式碼就是把代碼寫死了,導致彈性不足,降低了可擴充性,例如在代碼裡的

if...else...
switch...case...


這些代碼通常都屬於寫入程式碼,項目中的這些代碼多了,就相當於說明這個代碼的
靈活性、擴充性、彈性等等的少了。

所以,我們要盡量使用軟編碼,通俗點就是“別把話說死了,留點轉彎的餘地”
多態性就是這種軟編碼特性的反映,下面我們一起來研究一下多態性。

多態性是一種抽象,把事物的特徵抽象出來,然後事物的具體形態我們就不關心了。

例如對工人這種事物來說,他的特徵就是工作,至於是什麼工人,他做什麼工作,
我們就不用關心了,只要我們以“工人.工作”這種方式去調用。那他就會為我們工作了。

那為什麼我們不抽象出其他的特徵,只抽象出工作這個特徵呢?
因為我們只對這個特徵感興趣,他的什麼吃飯、睡覺、如廁等的特性我們都不關心了。
有了多態,我們就可以實現軟編碼了!

講解了多態的概念之後,我們來看看多態的實現(C++的實現):

多態的實現是通過虛函數表(VTable),每個類如果有虛函數,那它就有一個虛函數
表,所有的對象都共用這一個VTable。這個概念也叫做動態聯編,還有靜態聯編,這
些概念都是通過在程式執行的時候表現出來的性質來定的,我們下面會看看它的“動”
和“靜”究竟體現在哪裡。

先看一段代碼: 

class C0
...{
public:
    void Test()
    ...{
        cout << "call C0 Test()。" << endl;
    }
};

這個類沒有虛函數,調用的時候就是靜態調用。調用的代碼如下:

    // 靜態編譯(早綁定 early binding)
    C0 *pO0;
    C0 obj0;
    pO0 = &obj0;
    pO0->Test();

它的反組譯碼代碼如下:

// 直接調用函數(已經知道地址)
00401432   mov         ecx,dword ptr [ebp-0Ch]
00401435   call        @ILT+160(C0::Test) (004010a5)

下面看看帶虛函數的類:

class C1
...{
public:
    virtual void Test()
    ...{
        cout << "call C1 Test()" << endl;
    }
};

class C11 : public C1
...{
public:
    void Test()
    ...{
        cout << "call C11 Test()" << endl;
    }
};

它的調用:

    C11 obj11;

    C1 *pObj1;
    pObj1 = &obj11;
    // 這裡產生的彙編代碼
    // 0040144A   lea         edx,[ebp-14h]               // 定址找到pObj1
    // 0040144D   mov         dword ptr [ebp-1Ch],edx

    pObj1->Test();
    // 這裡產生的彙編代碼
    // 00401450   mov         eax,dword ptr [ebp-1Ch]  // 取得虛表地址
    // 00401453   mov         edx,dword ptr [eax]
    // 00401455   mov         esi,esp
    // 00401457   mov         ecx,dword ptr [ebp-1Ch]  // 根據虛表的位置來取得Test()函數
    // 0040145A   call        dword ptr [edx]           // 調用Test()函數

 根據上述的彙編代碼,我們可以知道,在多態調用函數的時候,程式執行以下步驟:
 1、定址找到pObj1
 2、由於C11重載了Test虛函數,所以*pObj1指向的就是C11的VTable的地址
 3、調用pObj1->Test()時,程式通過Vptr(虛表的指標,對象的首地址),找到VTable,再根據位移調用Test函數。

 由於上述的多態調用過程是一個動態過程(在運行時去“找”函數來調用),而不是編譯完就直接把函數地址擺在那裡了,所以被稱作“動態聯編”。

上面把多態的“動”和“靜”的特點結合代碼說了一遍,希望能說清楚了。

下面再驗證一個類的虛表的問題,如果你對虛表已經很熟悉了,就不用再往下看了。

在很多書上都已經說明了C++的物件模型,這裡只是做個驗證。看看這段代碼:

class C1
...{
public:
    virtual void Test()
    ...{
        cout << "call C1 Test()" << endl;
    }
};

class C11 : public C1
...{
public:
    void Test()
    ...{
        cout << "call C11 Test()" << endl;
    }
};

class C12 : public C1
...{
public:
    void Test()
    ...{
        cout << "call C12 Test()" << endl;
    }
};

我們可以知道 Test() 是虛函數,從C1派生的類必定有自己的虛表。而且根據別的資料,虛表指標是放在對象的首地址的,我們下面就來驗證一下:

    // 驗證首地址
    C11 obj110;
    C11 obj111;

    printf("obj110 的地址:%x ", &obj110);
    printf("obj111 的地址:%x ", &obj111);
    printf("obj110 虛表的地址:%x ", *(&obj110));
    printf("obj111 虛表的地址:%x ", *(&obj111));

結果是:

obj110 的地址:12ff7c
obj111 的地址:12ff78
obj110 虛表的地址:432098
obj111 虛表的地址:432098

由上面的結果我們可以驗證:
1、一個類一個VTABLE,而不是一個對象一個VTABLE。
2、對象的首地址的內容就是VTABLE的地址。

總結一下:
C++的多態性包括其概念和實現,本文從編譯器產生的程式碼來討論C++多態特性,特別說明了為什麼多態特性被稱為“動態聯編”,它和“靜態聯編”有什麼不同,它們的“動”與“靜”體現在哪裡。另外還對對象的虛表做了些驗證。好了,希望本文能對你認識C++的多態性有一定的協助!謝謝!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.