ATL布幔之下的祕密(2)

作者:Zeeshan Amjad
譯者:李馬 (home.nuc.edu.cn/~titilima)
原文出處: http://www.codeproject.com/atl/atl_underthehood_2.asp

介紹
   在本系列的教程中,我要討論一些ATL的內部工作方式以及它所使用的技術,這是本系列的第二篇文章。

   現在讓我們來探究一些虛函數背後更加有趣的資料。爲了和上文保持一致,在本文的討論中我將使用相同的順序,程序的序號從20開始。
   讓我們看看下面這個程序:
程序20.

#include <iostream>
using namespace std;

class Base {
public:
    virtual void fun() {
        cout << "Base::fun" << endl;
    }
    void show() {
        fun();
    }
};

class Drive : public Base {
public:
    virtual void fun() {
        cout << "Drive::fun" << endl;
    }
};

int main() {
    Drive d;
    d.show();

    return 0;
}

程序的輸出爲:
Drive::fun
這個程序清楚地示範了基類的函數是如何調用派生類的虛函數的。這一技術被用於不同的框架中,例如MFC和設計模式(比如Template Design Pattern)。現在你可以修改一下這個程序來看看它的行爲,我將要在基類的構造函數中調用虛函數,而不是普通的成員函數。

程序21.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        fun();
    }
    virtual void fun() {
        cout << "Base::fun" << endl;
    }
};

class Drive : public Base {
public:
    virtual void fun() {
        cout << "Drive::fun" << endl;
    }
};

int main() {
    Drive d;

    return 0;
}

程序的輸出爲:
base::fun
這個程序表明,我們不能在基類的構造函數中調用派生類的虛函數。好了,那就讓我們來看看着布幔之下到底做了什麼。我將會把這些構造函數之中的指針值打印出來,爲了簡便起見,我移除了類中其它的函數。
程序22.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "This Pointer = " << (int*)this << endl;
        cout << endl;
    }
virtual void f() { cout << "Base::f" << endl; }
};

class Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "This Pointer = " << (int*)this << endl;
        cout << endl;
    }
virtual void f() { cout << "Drive::f" << endl; }
};

int main() {
    Drive d;
    cout << "In Main" << endl;
    cout << (int*)&d << endl;

    return 0;
}

程序的輸出爲:
In Base
This Pointer = 0012FF7C

In Drive
This Pointer = 0012FF7C

In Main
0012FF7C
這就表示,整個內存位置中,只有一個對象的存在。那麼就讓我們把這個指針指向的值打印出來,也就是虛函數表的指針vptr指向的值,VTable的地址。
程序23.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
        cout << endl;
    }
    virtual void f1() { cout << "Base::f1" << endl; }
};

class Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
        cout << endl;
    }
    virtual void f1() { cout << "Drive::f2" << endl; }
};

int main() {
    Drive d;
    return 0;
}


程序的輸出爲:
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C08C
Value at Vtable = 004010F0

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C07C
Value at Vtable = 00401217
這個程序示範了基類和派生類中不同的虛函數表地址。爲了更好地弄懂這一問題,那就讓我們把繼承層次加深,並添加一個繼承於Drive類的MostDrive類,然後構建一個它的對象。
程序24.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
        cout << endl;
    }
    virtual void f1() { cout << "Base::f1" << endl; }
};

class Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
        cout << endl;
    }
    virtual void f1() { cout << "Drive::f2" << endl; }
};

class MostDrive : public Drive {
public:
    MostDrive() {
        cout << "In MostDrive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
        cout << endl;
    }
    virtual void f1() { cout << "MostDrive::f2" << endl; }
};

int main() {
    MostDrive d;
    return 0;
}

程序的輸出爲:
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0A0
Value at Vtable = 004010F5

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C090
Value at Vtable = 00401221

In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C080
Value at Vtable = 00401186
這個程序示範了虛函數表指針在每個類的構造函數中的初始化過程。這樣看來,每個類構造函數中虛函數表的地址是不同的,main函數則使用了繼承鏈中的最底部的派生類來創建對象。
   現在你可以看看虛函數表中各個類的構造函數的位置了,你可以將虛函數表中的第一個入口存入一個函數指針,並嘗試運行它。
程序25.
#include <iostream>
using namespace std;

typedef void(*Fun)();

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;

        Fun pFun = (Fun)*(int*)*(int*)this;
        pFun();
        cout << endl;
    }
    virtual void f1() { cout << "Base::f1" << endl; }
};

class Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
        
        Fun pFun = (Fun)*(int*)*(int*)this;
        pFun();
        cout << endl;
    }
    virtual void f1() { cout << "Drive::f1" << endl; }
};

class MostDrive : public Drive {
public:
    MostDrive() {
        cout << "In MostDrive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable = " << (int*)*(int*)*(int*)this << endl;
        
        Fun pFun = (Fun)*(int*)*(int*)this;
        pFun();
        cout << endl;
    }
    virtual void f1() { cout << "MostDrive::f1" << endl; }
};

int main() {
    MostDrive d;
    return 0;
}

程序的輸出爲:
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C098
Value at Vtable = 004010F5
Base::f1

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C088
Value at Vtable = 00401221
Drive::f1

In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C078
Value at Vtable = 00401186
MostDrive::f1
這個程序示範了每個類中的構造函數是如何用自己的虛函數來填充虛函數表中的各入口的。所以,Base類使用Base類的虛函數地址來填充自己的虛函數表,當Drive類的構造函數執行它的時候會創建另外一個虛函數表,並存儲自己的虛函數地址。
   現在,你會發現在基類中含有多個虛函數的情況下,派生類並不能完全重寫它們。
程序26.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << "Value at Vtable 3rd entry = " << (int*)*((int*)*(int*)this+2) << endl;
cout << endl;
    }
    virtual void f1() { cout << "Base::f1" << endl; }
    virtual void f2() { cout << "Base::f2" << endl; }
};

class Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << "Value at Vtable 3rd entry = " << (int*)*((int*)*(int*)this+2) << endl;
        cout << endl;
    }
    virtual void f1() { cout << "Drive::f1" << endl; }
};

int main() {
    Drive d;
    return 0;
}

程序的輸出爲:
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0E0
Value at Vtable 1st entry = 004010F0
Value at Vtable 2nd entry = 00401145
Value at Vtable 3rd entry = 00000000

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C8
Value at Vtable 1st entry = 0040121C
Value at Vtable 2nd entry = 00401145
Value at Vtable 3rd entry = 00000000
這個程序的輸出表明基類的虛函數在派生類中並未被重寫,然後,派生類的構造函數沒有對虛函數的入口做任何的事情。
   那麼現在,讓我邀請純虛函數來加入這一遊戲,再來看看它的行爲吧。請看以下的程序:
程序27.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() = 0;
    virtual void f2() = 0;
};

class Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() { cout << "Drive::f1" << endl; }
    virtual void f2() { cout << "Drive::f2" << endl; }
};

int main() {
    Drive d;

    return 0;
}

在debug和release模式下,程序的輸出有所不同。下面是debug模式的輸出:
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0BC
Value at Vtable 1st entry = 00420CB0
Value at Vtable 2nd entry = 00420CB0

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0A4
Value at Vtable 1st entry = 00401212
Value at Vtable 2nd entry = 0040128F
以下則是release模式的輸出:
In Base
Virtual Pointer = 0012FF80
Address of Vtable = 0042115C
Value at Vtable 1st entry = 0041245D
Value at Vtable 2nd entry = 0041245D

In Drive
Virtual Pointer = 0012FF80
Address of Vtable = 00421154
Value at Vtable 1st entry = 00401310
Value at Vtable 2nd entry = 00401380
爲了更好地弄懂這一原理,我們需要對程序作少許的改動,並嘗試使用函數指針來調用虛函數。

程序28.
#include <iostream>
using namespace std;

typedef void(*Fun)();

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        
        // 嘗試執行第一個虛函數
        Fun pFun = (Fun)*((int*)*(int*)this+0);
        pFun();

        cout << endl;
    }
    virtual void f1() = 0;
    virtual void f2() = 0;
};

class Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() { cout << "Drive::f1" << endl; }
    virtual void f2() { cout << "Drive::f2" << endl; }
};

int main() {
    Drive d;

    return 0;
}

現在程序的行爲在debug和release模式下仍然不同。在debug模式下,它會顯示一個運行時錯誤的對話框:

並且,當你按下“忽略”按鈕之後,它會顯示下面的對話框:

而在release模式下運行的話,它只會在控制檯窗口中輸出錯誤信息:
In Base
Virtual Pointer = 0012FF80
Address of Vtable = 0042115C
Value at Vtable 1st entry = 0041245D
Value at Vtable 2nd entry = 0041245D

runtime error R6025
- pure virtual function call
那麼這裏的R6025是什麼?它定義於CMSGS.H頭文件中,這個頭文件定義了所有C Run Time Library的所有錯誤信息。
#define _RT_PUREVIRT_TXT   "R6025" EOL "- pure virtual function call" EOL
事實上,當我們定義了純虛函數後,編譯器就會放置一個名爲_purecall的C Run Time Library的函數地址。這個函數定義在PUREVIRT.C之中,它的原型如下:
void __cdecl _purecall(void); // 譯註:原文此處無分號
我們可以在程序中直接調用這個函數來達到相同的效果,請看下面這個小程序:

程序29.
int main() {
    _purecall();    
    return 0;
}


這個程序在debug模式和release模式下的輸出和前一個是一樣的。爲了更好的理解這個問題,讓我們把繼承鏈弄得更深一些,並且從Drive類中再繼承一個類來看看效果吧。
程序30.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() = 0;
    virtual void f2() = 0;
};

class Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
};

class MostDrive : public Drive {
public:
    MostDrive() {
        cout << "In MostDrive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() { cout << "MostDrive::f1" << endl; }
    virtual void f2() { cout << "MostDrive::f2" << endl; }
};

int main() {
    MostDrive d;

    return 0;
}

程序的輸出爲:
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0D8
Value at Vtable 1st entry = 00420F40
Value at Vtable 2nd entry = 00420F40

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C0
Value at Vtable 1st entry = 00420F40
Value at Vtable 2nd entry = 00420F40

In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0A8
Value at Vtable 1st entry = 00401186
Value at Vtable 2nd entry = 004010F5

這個程序表明,Base和Drive類是用相同的值來初始化各自的虛函數表的。那麼,如果繼承更深一些,並且只有最底層的派生類重寫了純虛函數,在這種情況下又會發生什麼呢?這就是在COM程序設計的情況下所發生的了——接口就是隻擁有純虛函數的類,並且一個接口是繼承自另一個接口的,只有實現類纔會重寫接口的純虛函數。這樣一來,每個基類的構造函數就會以相同的值來初始化它們自己的虛函數表入口。所以,這就意味着相同的代碼會反覆重複下去。
   ATL的主要思想就是讓COM組件儘可能的小,但是由於這一行爲,接口類的構造函數就會擁有很多不必要的代碼。爲了解決這一問題,ATL引入了一個宏ATL_NO_VTABLE,它定義在ATLDEF.H中:

#define ATL_NO_VTABLE __declspec(novtable)

__declspec(novtable)爲Microsoft C++擴展的類屬性。它會使編譯器不產生初始化虛函數表指針和虛函數表的代碼,這樣一來就減少了生成代碼的尺寸。
   那麼,我們來修改一下我們的代碼,這樣就能更好的明白這一屬性究竟能爲我們做什麼了。
程序31.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() = 0;
    virtual void f2() = 0;
};

class Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
};

class __declspec(novtable) MostDrive : public Drive {
public:
    MostDrive() {
        cout << "In MostDrive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() { cout << "MostDrive::f1" << endl; }
    virtual void f2() { cout << "MostDrive::f2" << endl; }
};

int main() {
    MostDrive d;

    return 0;
}


程序的輸出爲:
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0CC
Value at Vtable 1st entry = 00420E60
Value at Vtable 2nd entry = 00420E60

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0B4
Value at Vtable 1st entry = 00420E60
Value at Vtable 2nd entry = 00420E60

In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0B4
Value at Vtable 1st entry = 00420E60
Value at Vtable 2nd entry = 00420E60
這個程序有另外一個結果,也就是Drive和MostDrive類擁有相同的虛函數表地址,但是Base類卻不同。事實上,這就是由於我們沒有對Base類使用__declspec(novtable)屬性的緣故。現在,我們來對繼承的Drive類也使用相同的屬性,也就是__declspec(novtable)。
程序32.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() = 0;
    virtual void f2() = 0;
};

class __declspec(novtable) Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
};

class __declspec(novtable) MostDrive : public Drive {
public:
    MostDrive() {
        cout << "In MostDrive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() { cout << "MostDrive::f1" << endl; }
    virtual void f2() { cout << "MostDrive::f2" << endl; }
};

int main() {
    MostDrive d;

    return 0;
}

   現在,程序的輸出爲:
In Base
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C0
Value at Vtable 1st entry = 00420E50
Value at Vtable 2nd entry = 00420E50

In Drive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C0
Value at Vtable 1st entry = 00420E50
Value at Vtable 2nd entry = 00420E50

In MostDrive
Virtual Pointer = 0012FF7C
Address of Vtable = 0046C0C0
Value at Vtable 1st entry = 00420E50
Value at Vtable 2nd entry = 00420E50
在MSDN中,對__declspec(novtable)的解釋是:它應該使用在純虛類中。那麼,讓我們再做一個實驗來更好地弄懂這一含義吧。
程序33.
#include <iostream>
using namespace std;

class Base {
public:
    Base() {
        cout << "In Base" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
    virtual void f1() = 0;
    virtual void f2() = 0;
};

class __declspec(novtable) Drive : public Base {
public:
    Drive() {
        cout << "In Drive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;
    }
};

class __declspec(novtable) MostDrive : public Drive {
public:
    MostDrive() {
        cout << "In MostDrive" << endl;
        cout << "Virtual Pointer = " << (int*)this << endl;
        cout << "Address of Vtable = " << (int*)*(int*)this << endl;
        cout << "Value at Vtable 1st entry = " << (int*)*((int*)*(int*)this+0) << endl;
        cout << "Value at Vtable 2nd entry = " << (int*)*((int*)*(int*)this+1) << endl;
        cout << endl;

        // 嘗試調用第一個虛函數
        typedef void (*Fun)();
        Fun pFun = (Fun)*((int*)*(int*)this+0);
        pFun();

    }
    virtual void f1() { cout << "MostDrive::f1" << endl; }
    virtual void f2() { cout << "MostDrive::f2" << endl; }
};

int main() {
    MostDrive d;

    return 0;
}


我們在程序中添加的新東西是:
// 嘗試調用第一個虛函數
typedef void (*Fun)();
Fun pFun = (Fun)*((int*)*(int*)this+0);
pFun();
並且,當我們運行這個應用程序的時候,我們會面對與前一個程序相同的問題——也就是嘗試調用虛函數發生的錯誤。這就意味着虛函數表並未初始化。MostDrive類並不是一個抽象類,所以我們應該從類中移除__declspec(novtable)。
程序34.
#include <iostream>
using namespace std;

class Base {
public:
    virtual void f1() = 0;
    virtual void f2() = 0;
};

class __declspec(novtable) Drive : public Base {
};

class MostDrive : public Drive {
public:
    MostDrive() {

        // 嘗試調用第一個虛函數
        typedef void (*Fun)();
        Fun pFun = (Fun)*((int*)*(int*)this+0);
        pFun();

    }
    virtual void f1() { cout << "MostDrive::f1" << endl; }
    virtual void f2() { cout << "MostDrive::f2" << endl; }
};

int main() {
    MostDrive d;

    return 0;
}


現在,程序就可以正常工作了。它的輸出爲:
MostDrive::f1
這個屬性並不一定只用在ATL的類中,它可以對任何不能創建對象的類使用。同樣,它也並不一定非要用在ATL類中,也就是說它可以從ATL類中省略,不過這就意味着這樣會產生更多的代碼。
   我希望能在下篇文章中探究更多ATL的祕密。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章