C++编译期间的虚函数调用机制

来源:互联网 发布:centos pppoe拨号上网 编辑:程序博客网 时间:2024/06/03 07:30

C++编译期间的虚函数调用机制

class  CMyWnd : public CWindowImpl<CMyWnd>

{

    ...

}; 

这样作是合法的,因为C++的语法解释说即使CMyWnd类只是被部分定义,类名CMyWnd已经被列入递归继承列表,是可以使用的。将类名作为模板类的参数是因为ATL要做另一件诡秘的事情,那就是编译期间的虚函数调用机制(传输中的静多态)

如果你想要了解它是如何工作地,请看下面的例子:

template <class T>

class B1

{

public:

    void SayHi()

    {

        T* pT = static_cast<T*>(this);   // 展开模板你就会明白

 

        pT->PrintClassName();

    }

protected:

    void PrintClassName() { cout << "This is B1"; }

};

 

class D1 : public B1<D1>

{

    // No overridden functions at all

};

 

class D2 : public B1<D2>

{

protected:

    void PrintClassName() { cout << "This is D2"; }

};

 

main()

{

    D1 d1;

    D2 d2;

 

    d1.SayHi();    // prints "This is B1"

    d2.SayHi();    // prints "This is D2"

}

这句代码static_cast<T*>(this) 就是窍门所在。它根据函数调用时的特殊处理将指向B1类型的指针this指派为D1D2类型的指针,因为模板代码是在编译其间生成的,所以只要编译器生成正确的继承列表,这样指派就是安全的。(如果你写成:

class D3 : public B1<D2>

就会有麻烦) 之所以安全是因为this对象只可能是指向D1D2(在某些情况下)类型的对象,不会是其他的东西。注意这很像C++的多态性(polymorphism),只是SayHi()方法不是虚函数。

要解释这是如何工作的,首先看对每个SayHi()函数的调用,在第一个函数调用,对象B1被指派为D1,所以代码被解释成:

void B1<D1>::SayHi()

{

    D1* pT = static_cast<D1*>(this);  // 为什么可以转呢, 因为D1继承B1

 

    pT->PrintClassName();

}

由于D1没有重载PrintClassName(),所以查看基类B1B1PrintClassName(),所以B1PrintClassName()被调用。

现在看第二个函数调用SayHi(),这一次对象被指派为D2类型,SayHi()被解释成:

void B1<D2>::SayHi()

{

    D2* pT = static_cast<D2*>(this);

 

    pT->PrintClassName();

}

这一次,D2含有PrintClassName()方法,所以D2PrintClassName()方法被调用。

这种技术的有利之处在于:

  • 不需要使用指向对象的指针
  • 节省内存,因为不需要虚函数表
  • 因为没有虚函数表所以不会发生在运行时调用空指针指向的虚函数
  • 所有的函数调用在编译时确定(译者加:区别于C++的虚函数机制使用的动态编连),有利于编译程序对代码的优化。

节省虚函数表在这个例子中看起来无足轻重(每个虚函数只有4个字节),但是设想一下如果有15个基类,每个类含有20个方法,加起来就相当可观了。

要熟悉C++的对象模型.

原创粉丝点击