国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > C++ > 正文

C++ COM編程之接口背后的虛函數(shù)表

2020-01-26 15:17:03
字體:
來源:轉載
供稿:網(wǎng)友

前言

學習C++的人,肯定都知道多態(tài)機制;多態(tài)就是用父類型別的指針指向其子類的實例,然后通過父類的指針調用實際子類的成員函數(shù)。對于多態(tài)機制是如何實現(xiàn)的,你有沒有想過呢?而COM中的接口就將這一機制運用到了極致,所以,不知道多態(tài)機制的人,是永運無法明白COM的。所以,在總結COM時,是非常有必要專門總結一下C++的多態(tài)機制是如何實現(xiàn)的。

多態(tài)

什么是多態(tài)?上面也說了,多態(tài)就是用父類型別的指針指向其子類的實例,然后通過父類的指針調用實際子類的成員函數(shù)。現(xiàn)在通過代碼,讓大家切身的體會一下多態(tài):

復制代碼 代碼如下:

#include <iostream>
using namespace std;
 
class A
{
public:
    void Print()
    {
        cout<<"I am A."<<endl;
    }
};
 
class B : public A
{
public:
    void Print()
    {
        cout<<"I am B."<<endl;
    }
};
 
int main()
{
    A *pAObj = new B();
    pAObj->Print();
}

上面代碼的運行結果是:I am A.這不是多態(tài)的行為。

好了,經(jīng)過對上面代碼的改造,就在A類的Print函數(shù)前面加入關鍵字virtual,具體代碼如下:

復制代碼 代碼如下:

#include <iostream>
using namespace std;
 
class A
{
public:
    virtual void Print()
    {
        cout<<"I am A."<<endl;
    }
};
 
class B : public A
{
public:
    void Print()
    {
        cout<<"I am B."<<endl;
    }
};
 
int main()
{
    A *pAObj = new B();
    pAObj->Print();
}

此時,代碼的運行結果為:I am B.這個時候就表現(xiàn)出來了多態(tài)行為。好了,多了我也不說了,就通過這個簡單的例子,你就能體會到多態(tài)的概念了。從下面才開始今天的主題。

虛函數(shù)表

多態(tài)機制的關鍵就是在于虛函數(shù)表,也就是vtbl。當我們定義一個類,類中包含虛函數(shù)時,其實也就定義了一張?zhí)摵瘮?shù)表,沒有虛函數(shù)的類是不包含虛函數(shù)表的,只有該類被實例化時,才會將這個表分配到這個實例的內存中;在這張?zhí)摵瘮?shù)表中,存放了每個虛函數(shù)的地址;它就像一個地圖一樣,指明了實際所應該調用的函數(shù)。比如我定義一個類,如下:

復制代碼 代碼如下:

class CIF
{
public:
     CIF(){}
     CIF(int i, int f) : m_iVar(i), m_fVar(f){}
     virtual void IF1() { cout<<"I'm IF1"<<endl; }
     virtual void IF2() { cout<<"I'm IF2"<<endl; }
     virtual void IF3() { cout<<"I'm IF3"<<endl; }
     void MemFunc(){ cout<<"I'm IF4"<<endl; }
private:
     int m_iVar;
     float m_fVar;
};

這樣的一個類,當你去定義這個類的實例時,編譯器會給這個類分配一個成員變量,該變量指向這個虛函數(shù)表,這個虛函數(shù)表中的每一項都會記錄對應的虛函數(shù)的地址;如下圖:

這個類的變量還沒有被初始化時,就像上圖那樣,變量的值都是隨機值,而指向虛擬函數(shù)表的指針__vfptr中對應的虛函數(shù)地址也是錯誤的地址;只有等我們真正的完成了這個變量的聲明和初始化時,這些值才能被正確的初始化,如下圖:

從上圖中就可以看到,初始化完成以后,指向虛函數(shù)表的__vfptr指針中的元素都被賦予了正確的虛函數(shù)值,分別指向了在類中定義的三個虛函數(shù)。也看到了,__vfptr指針定義的位置也比m_iVar和m_fVar變量的位置靠前;在C++編譯器中,它保證虛函數(shù)表的指針存在于對象實例中最前面的位置,這主要是為了在多層繼承或是多重繼承的情況下,能以高性能取到這張?zhí)摵瘮?shù)表,然后進行遍歷,查找對應的虛函數(shù)指針,進行對應的調用。

我們都知道,虛函數(shù)是用來支持C++中的多態(tài)的,而單獨的一個類,有了虛函數(shù),而沒有任何繼承關系,也就是說沒有子類去覆蓋父類的虛函數(shù),這樣是毫無意義的。所以下面就要從各個方面進行詳細的說明虛函數(shù)表。

沒有實現(xiàn)多態(tài)的單繼承

比如有如下的繼承關系:

在這個繼承關系中,CIF2作為CIF1的子類,但是CIF2沒有重寫CIF1類的任何虛函數(shù);定義CIF2 if2Obj;實例,在派生類的實例中,它的虛函數(shù)表應該是像下面這樣的:

復制代碼 代碼如下:

[0]     0x011513c5 {InterfaceDemo2.exe!CIF1::IF1(void)}     void *
[1]     0x011512cb {InterfaceDemo2.exe!CIF1::IF2(void)}     void *
[2]     0x01151343 {InterfaceDemo2.exe!CIF1::IF3(void)}     void *
[3]     0x01151249 {InterfaceDemo2.exe!CIF2::IF4(void)}     void *
[4]     0x01151433 {InterfaceDemo2.exe!CIF2::IF5(void)}     void *
[5]     0x01151267 {InterfaceDemo2.exe!CIF2::IF6(void)}     void *
[6]     0x00000000     void *

可以發(fā)現(xiàn),虛函數(shù)按照其聲明順序存放在表中,父類的虛函數(shù)在子類的虛函數(shù)前面。

實現(xiàn)多態(tài)的單繼承

現(xiàn)在我在CIF2類中,重寫CIF1類的IF1函數(shù),它們的關系如下:

在上圖中,CIF2繼承了CIF1,并且在CIF2類中重寫了CIF1的虛函數(shù)IF1,那我們現(xiàn)在看看虛函數(shù)表是什么樣子的?

復制代碼 代碼如下:

[0]     0x00b61311 {InterfaceDemo2.exe!CIF2::IF1(void)}     void *
[1]     0x00b612c6 {InterfaceDemo2.exe!CIF1::IF2(void)}     void *
[2]     0x00b61343 {InterfaceDemo2.exe!CIF1::IF3(void)}     void *
[3]     0x00b61249 {InterfaceDemo2.exe!CIF2::IF4(void)}     void *
[4]     0x00b61433 {InterfaceDemo2.exe!CIF2::IF5(void)}     void *
[5]     0x00000000     void *

你發(fā)現(xiàn)了什么?虛函數(shù)表中的第一項是CIF2::IF1,而不是CIF1::IF1,這說明了當在子類中重寫父類的虛函數(shù)時,新的函數(shù)的地址覆蓋了父類的虛函數(shù)地址,這樣就能在多態(tài)時能正確的找到需要被調用的函數(shù);而沒有被覆蓋的函數(shù)還是那樣的順序在虛函數(shù)表中存儲著。

沒有實現(xiàn)多態(tài)的多繼承

對于簡單的,沒有實現(xiàn)多態(tài)的多繼承,比如,有下面的一個多繼承關系:

在子類中沒有重寫任何父類的虛函數(shù),那么它的虛函數(shù)表應該是什么樣子呢?

虛函數(shù)表CIF1,如下:

復制代碼 代碼如下:

[0]     0x001e13d9 {InterfaceDemo2.exe!CIF1::IF1(void)}     void *
[1]     0x001e12df {InterfaceDemo2.exe!CIF1::IF2(void)}     void *
[2]     0x001e1357 {InterfaceDemo2.exe!CIF1::IF3(void)}     void *
[3]     0x001e10c8 {InterfaceDemo2.exe!CIF3::IF4(void)}     void *
[4]     0x001e1041 {InterfaceDemo2.exe!CIF3::IF5(void)}     void *
[5]     0x001e1249 {InterfaceDemo2.exe!CIF3::IF6(void)}     void *
[6]     0x00000000     void *

虛函數(shù)表CIF2,如下:

復制代碼 代碼如下:

[0]     0x001e1258 {InterfaceDemo2.exe!CIF2::IF7(void)}     void *
[1]     0x001e1447 {InterfaceDemo2.exe!CIF2::IF8(void)}     void *
[2]     0x001e127b {InterfaceDemo2.exe!CIF2::IF9(void)}     void *
[3]     0x00000000     void *

從上面的虛函數(shù)表,我們可以分析出來,每個父類都有自己的虛函數(shù)表,子類的虛函數(shù)被放到了第一個父類的表中。第一個父類是按照聲明順序來判斷的。

實現(xiàn)多態(tài)的多繼承

上面說的是沒有發(fā)生重寫的情況,現(xiàn)在來說說發(fā)生重寫的情況;比如,現(xiàn)在有以下情況:

在子類中重寫了父類的虛函數(shù),那它的虛函數(shù)表又是什么樣子呢?

虛函數(shù)表CIF1,如下:

復制代碼 代碼如下:

[0]     0x012013cf {InterfaceDemo2.exe!CIF3::IF1(void)}     void *
[1]     0x012012d5 {InterfaceDemo2.exe!CIF1::IF2(void)}     void *
[2]     0x0120134d {InterfaceDemo2.exe!CIF1::IF3(void)}     void *
[3]     0x01201456 {InterfaceDemo2.exe!CIF3::IF4(void)}     void *
[4]     0x012014d8 {InterfaceDemo2.exe!CIF3::IF5(void)}     void *
[5]     0x00000000     void *

虛函數(shù)表CIF2,如下:

復制代碼 代碼如下:

[0]     0x012014e2 {InterfaceDemo2.exe![thunk]:CIF3::IF1`adjustor{4}' (void)}     void *
[1]     0x012014ce {InterfaceDemo2.exe!CIF2::IF2(void)}     void *
[2]     0x012014d3 {InterfaceDemo2.exe!CIF2::IF3(void)}     void *
[3]     0x00000000     void *

從上面的虛函數(shù)表中,我們可以看到虛函數(shù)表中的CIF1::IF1(void)全都被替換成了CIF3::IF1(void),那么我們就可以以任意的父類指針來調用IF1(void),實際上調用的是CIF3::IF1(void),這就實現(xiàn)了所謂的多態(tài)。

總結

總結了這么多關于虛函數(shù)表的內容,感覺很扯,和接口沒有多大的關系;但是,這一切都是COM的基礎,COM的背后,就是接口,而接口的背后,就是我這里總結的,說白了,完全了解了這里,對于理解COM的接口是有非常大的用處的。希望我的總結對大家有用。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 辽中县| 张家口市| 文山县| 郁南县| 萍乡市| 清丰县| 陇西县| 古交市| 婺源县| 漳平市| 温州市| 大田县| 凌源市| 荃湾区| 边坝县| 儋州市| 阜宁县| 赤峰市| 沐川县| 鸡泽县| 邹平县| 桐城市| 阿合奇县| 蕉岭县| 平山县| 台湾省| 陆良县| 新晃| 泰顺县| 彭水| 广宁县| 蓬莱市| 昌乐县| 宝山区| 西盟| 土默特右旗| 荔波县| 江阴市| 沂南县| 永济市| 河源市|