模拟虚函数?
近几天看《ATL INTERNALS》,看到了附录中的一个关于template的小技巧-仿真动态绑定:
template class Array { public: …… virtual int Compare(const Array & rhs) =0; bool operator< (const Array & rhs) { return this->Compare(rhs) < 0; } bool operator> (const Array & rhs) { return this->Compare(rhs) >0; } bool operator== (const Array & rhs) { return this->Compare(rhs) == 0; } T m_rg[1024]; };
然后派生类重写这个虚函数获得多态特性:
class String : public Array { public: int Compare(const Array & rhs) { return strcmp(m_rg, rhs.m_rg); } };
为了实现虚函数需要一个vptr和一个vtable,以及需要至少经过两次提领操作才能调用正确的函数。
然后书中介绍了一种提供效率的办法:
template <typename T, typename Deriving> class Array { public: …… bool operator< (const Array & rhs) { return static_cast (this)->Compare(rhs) < 0; } bool operator> (const Array & rhs) { return static_cast (this)->Compare(rhs) > 0; } bool operator== (const Array & rhs) { return static_cast (this)->Compare(rhs) == 0; } T m_rg[1024]; };
注意Array模板接受一个附加的参数——派生类的名字。它利用这个类名完成堆自己的静态强制转换。因为编译器会在实例化(具象化)派 生类的同时展开基类的代码,所以静态强制转换完成了一个完全安全的向下转换。
class String : public Array<char, String> { public: int Compare(const Array & rhs) { return strcmp(m_rg, rhs.m_rg); } };
这项技术在不使用虚成员的情况下使得我们看到并且感受到了动态绑定。真的有 那么神奇吗?虚函数真的可用通过模板来模拟?当然不是
一开始的时候我还觉得迷惑,但是隐约觉得这个东西只是看上去像虚函数,但是限制多多。于是上网去问高手。gigix一开始被我说晕了, 也怪我表述不好。问babysloth,一列出代码他就知道了这个是什么了。他说: “这叫curiously recurring template pattern, 由bell-lab的james coplien首先记录下来”
紧接着,我偶然看到以前下载的ATL相关技术解析的文章,里面详细说了这个东西。文章是Codeguru上的《ATL Under the Hood Part 3》。 简短的一个程序说明了这个东西:
#include using namespace std; class Base { public: virtual void fun() { cout << "Base::fun" << endl; } void doSomething() { fun(); } }; class Drive : public Base { public: void fun() { cout << "Drive::fun" << endl; } }; int main() { Drive obj; obj.doSomething(); return 0; }
这段程序中的行为和下面的这段程序是一样的:
#include using namespace std; template class Base { public: void fun() { cout << "Base::fun" << endl; } void doSomething() { T* pT = static_cast (this); pT->fun(); } }; class Drive : public Base { public: void fun() { cout << "Drive::fun" << endl; } }; int main() { Drive obj; obj.doSomething(); return 0; }
这里说的结果是一样的。引用虫虫的一句话:“效率不同”。
问题就是这里只是表现了虚函数的一个部分,虚函数一个经典的例子是用数组来保存派生类指针,通过指针调用被改写了的虚函数来表现多 态。用这个模拟的版本可以这么作吗?不行!
下面这么作是不行的:
#include using namespace std; template class Base { public: void fun() { cout << "Base::fun" << endl; } void doSomething() { T* pT = static_cast (this); pT->fun(); } }; class Drive1 : public Base { public: void fun() { cout << "Drive1::fun" << endl; } }; class Drive2 : public Base { public: void fun() { cout << "Drive2::fun" << endl; } }; int main() { Base * pBase = NULL; pBase = new Drive1; pBase->doSomething(); delete pBase; pBase = new Drive2; pBase->doSomething(); return 0; }
因为Drive2与Drive1不匹配。另外这么模拟虚函数只能维持一层,不能像真正的虚函数一样在派生的层次中任意的往下,虚函数一直都是虚 函数。例如:
#include using namespace std; template class Base { public: void fun() { cout << "Base::fun" << endl; } void doSomething() { T* pT = static_cast (this); pT->fun(); } }; class Drive : public Base { 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; }
输出是Drive::fun而不是MostDrive::fun
结论
结论就是这里并不是什么模拟虚函数,只是在这个情景下很像虚函数,虚函数的几个重要的性质并没有支持。这个只不过是一个程序设计中 的idiom,关于模板的诸多技巧中的一个。
主要用途:父类需要子类信息的时候。在父类中的一个函数a中需要调用子类中的某个函数b,以实现子类定制某些行为。
主要原理:通过对this的强制类型转换实现对pT->fun()的不同解释。这种解释是在编译期间的。编译期间展开模板的类型参数,根据参数 确定了this的转换到的类型,从而也确定了pT->fun()的解释。所以模拟的是静态的多态性。
使用方法:使用的方法不是像虚函数一样通过对象的指针调用虚函数实现多态。而是通过调用从基类继承来的某个普通函数,在该函数中再 去调用调用“经过改写”的派生类的函数。
主要缺陷:就是虚拟性质不具有传递性,不具有动态的多态性。所谓的多态也只不过是通过你传递的模板参数所隐含的意思决定具体的函数 调用。
感想:按照babysloth说法,这个也是一个设计模式。加上前段时间看《STL源码剖析》感受的iterator等模式,觉得设计模式真是博大精深 。等期中考试搞定了数学分析之后一定要向gigix借几本这方面的书看看。