在模板的帮助下,我们可以实现与之相同的行为。
程序50.
#include <iostream> using namespace std;
template <typename T> class Base { public: void fun() { cout << "Base::fun" << endl; }
void doSomething() { T* pT = static_cast<T*>(this); pT->fun(); } };
class Drive : public Base<Drive> { public: void fun() { cout << "Drive::fun" << endl; } };
int main() { Drive obj; obj.doSomething();
return 0; } | 程序的输出和前一个是一样的,所以我们可以用模板来模拟虚函数的行为。
程序中一个有趣的地方为:
class Drive : public Base<Drive> { 这表明我们可以将Drive类作为一个模板参数来传递。程序中另外一个有趣的地方是基类中的doSomething函数。 T* pT = static_cast<T*>(this); pT->fun(); |
在这里基类的指针被转换为派生类的指针,因为派生类是作为Base类的模板参数传递的。这个函数可以通过指针来执行,由于指针指向了派生类的对象,所以派生类的对象就被调用了。
但是这就有一个问题了:我们为什么要这样做?答案是:这样可以节省虚函数带有的额外开销,也就是虚函数表指针、虚函数表以及节省了调用虚函数所花费的额外时间。这就是ATL中使组件尽可能小、尽可能快的主要思想。
现在,你的脑海中可能会浮现另外一个问题。如果依靠这一开销更少的技术可以模拟虚函数的话,那我们为什么还要调用虚函数呢?我们不应该用这一技术替换所有的虚函数吗?对于这一问题,我可以简短地回答你:不,我们不能用这一技术替换虚函数。
其实这一技术还存在一些问题。第一,你不能从Drive类进行更深层的继承,如果你试着这么做,那么它将不再会是虚函数的行为了。而对于虚函数来说,这一切就不会发生。一旦你将函数声明为虚函数,那么在派生类中的所有函数都会成为虚函数,无论继承链有多深。现在我们看看当从Drive中再继承一个类的时候会发生什么。
程序51.
#include <iostream> using namespace std;
template <typename T> class Base { public: void fun() { cout << "Base::fun" << endl; }
void doSomething() { T* pT = static_cast<T*>(this); pT->fun(); } };
class Drive : public Base<Drive> { public: void fun() { cout << "Drive::fun" << endl; } };
class MostDrive : public Drive { public: void fun() { cout << "MostDrive::fun" << endl; } };
int main() { MostDrive obj; obj.doSomething();
return 0; } |
程序的输出和前一个一样。但是对于虚函数的情况来说,输出就应该是: MostDrive::fun
这一技术还有另外一个问题,就是当我们使用Base类的指针来存储派生类的地址的时候。
程序52.
#include <iostream> using namespace std;
template <typename T> class Base { public: void fun() { cout << "Base::fun" << endl; }
void doSomething() { T* pT = static_cast<T*>(this); pT->fun(); } };
class Drive : public Base<Drive> { public: void fun() { cout << "Drive::fun" << endl; } };
int main() { Base* pBase = NULL; pBase = new Drive;
return 0; } |
这个程序会给出一个错误,因为我们没有向基类传递模板参数。现在我们稍微修改一下,并传递模板参数。
|