虚拟函数表和C++抽象基类

一.虚拟函数表

      为什么用C++的纯抽象基类来实现COM接口是可行的呢?这主要是由于纯抽象类说定义的内存结构可以满足COM对接口的需求。

当定义一个纯抽象基类时,实际上定义的是一个内存块的结构,且其所有的实现都是一些具有相同的基本结构的内存块。但是,此内存只有在派生类中实现此抽象基类时才会被分配。继承抽象基类的派生类,也将继承此内存结构。

interface IX
{
   virtual void __stdcall fx1() = 0;
   virtual void __stdcall fx2() = 0;
   virtual void __stdcall fx3() = 0;
   virtual void __stdcall fx4() = 0;
};


           由图1可以看出,一个纯抽象基类所定义的内存结构包含两部分:虚拟函数表vTable和指向vTable的指针vTable Pointer。其中,指向抽象基类的指针指向此vTable指针,而vTable指针则指向虚拟函数表vTablevTable中包含一组指向虚拟函数实现的指针,例如图1的第一项为派生类中所实现的fx1函数的地址,第二项则是fx2的地址....

            COM接口的内存结构同C++编译器为抽象基类所生成的内存结构是相同的。因此,可以合用抽象基类来定义COM接口。所上面的COM抽象基类IX是一个COM接口,是由于其内存结构符合COM规范的要求。但是,对于一个COM接口还有其他的一些需求。例如,所有的COM接口都必须继承一个名为IUnkown的接口。这意味着,所有COM接口的前三个项都是相同的,其中保存的是IUnkown中三个成员函数的实现的地址。

二.vTable指针及实例数据

    vTable指针有什么用途呢?

vTable指针在由抽象基类函数指针到函数的过程中增加了一个额外的级别,正是这一额外的级别给接口的实现带来了极大的灵活性。

C++编译器生成代码时,实现抽象基类的类可能会将特定于实例的信息同vTable一块保存。例如,CA实现了抽象基类IX,代码如下:

class CA: public IX
{
public:

    //implement interface IX
    virtual void __stdcall fx1() {cout << “CA::fx1” << endl;}
    virtual void __stdcall fx2() {cout << “CA::fx2” << endl;}
    virtual void __stdcall fx3() {cout << “CA::fx3” << endl;}
    virtual void __stdcall fx4() {cout << “CA::fx4” << endl;}
     //constructor
     CA(int i):m_ix1(i), m_ix2(i*i), m_ix3(i*i*i)
     {}

     //interface data
     int m_ix1;
     int m_ix2;
     int m_ix3;
};

   对于上面我们说说的编译器,vTableCA的类数据将如图2所示。需要注意的是,实例数据理应是可以通过指向类的指针pA来访问的,但是由于客户通常并不知道实例数据是如何保存的,因此客户也就无法访问它们了。


虽然C++可以直接操作和使用实例数据,但COM组件绝不会访问任何实例数据。在COM中,对一个组件的访问只能通过函数完成,而绝不能直接通过变量来访问。此外,纯抽象基类只有虚拟函数,没有任何实例数据

三:多重实例

         vTable的作用决不仅仅是给实例数据的保存提供一个方便的位置,实际上,同一个类的不同实例还可以共享同一vTable。如果我们们建立了CA的两个不同的实例,那么将会有两组不同的实例数据,但不同的实例可以共享同一vTable及相同的实现(图3)。如:

int main()
{

//first instance of CA.
   CA* pA1 = new CA(2);
//second instance of CA
  CA* pA2 = new CA(3);
}

可是,虽然COM组件可以使用vTable指针来共享vTable,但这一点并不是必须的。COM组件的每一个实例都有一个不同的vTable

四:不同的类,相同的vTable 

           接口的真正威力在于继承此接口的所有类均可以被客户按同一方式进行处理。例如,类CB也继承了IX

class CB: public IX
{
 public:

     //implement interface IX
    virtual void __stdcall fx1() {cout << “CB::fx1” << endl;}
    virtual void __stdcall fx2() {cout << “CB::fx2” << endl;}
    virtual void __stdcall fx3() {cout << “CB::fx3” << endl;}
    virtual void __stdcall fx4() {cout << “CB::fx4” << endl;}
};

这样一来,客户就可以通过同一个IX指针来访问CA和CB。

void f(IX* pIX)
{
    pIX->fx1();
    pIX->fx2();

}

int main()

{
   //create instance of CA
   CA* pA = new CA(2);

   //create instance of CB
   CB* pB = new CB;

//get IX pointer to CA

 IX* pIX = pA;
 f(pIX);

//get IX pointer to CB
  pIX = pB;
  f(pIX);
}

在这里,我们将CACB都当作IX接口来使用,这是多态的一个例子。此例子的内存结构如图4所示。两个类CACB分别具有不用的实例数据、vTable以及实现。但因它们的vTable具有相同的格式而可以按相同的方式来访问。


4的这种格式是编译器根据抽象基类的定义而生成的。在某个类实现抽象基类时,此时他将被强制使用此种格式。对于组件也将是这样的。当组件返回一个IX接口指针时,它必须保证此指针指向正确的结构。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章