虚函数带来的额外CPU消耗
考虑如下的代码:
class D {
public:
int num;
D(int i = 0) { num = i; }
virtual void print() { cout << "I'm a D. my num=" << num << endl; }
};
class E :public D {
public:
E(int i = 0) { num = i; }
void print() { cout << "I'm a E. my num=" << num << endl; }
void not_virtual_print() { cout << "not virtual func" << num << endl; }
};
int main()
{
E* e = new E(1);
e->print();
e->not_virtual_print();
delete e;
return 0;
}
注意如下的虚函数调用和普通成员函数调用的汇编代码:
e->print();
008C2788 mov eax,dword ptr [e]
008C278B mov edx,dword ptr [eax]
008C278D mov esi,esp
008C278F mov ecx,dword ptr [e]
008C2792 mov eax,dword ptr [edx]
008C2794 call eax
008C2796 cmp esi,esp
008C2798 call __RTC_CheckEsp (08C1195h)
e->not_virtual_print();
008C279D mov ecx,dword ptr [e]
008C27A0 call E::not_virtual_print (08C14A1h)
二者差了很多行,明显虚函数额外消耗了CPU资源,主要是消耗在了多次打开指针获取地址,这也是运行时多态的特点。因为:虚函数的调用过程是跳到虚函数表->打开虚函数表中的虚函数指针->依据指针跳到真实函数体所在的位置。而成员函数的执行过程则是直接跳到真实函数体的位置。
舍弃虚函数,拥抱成员函数
然而大多数时候,我们明确知道对象E要调用自己重写的虚函数,每次调用e->print()都去查找虚函数表是无意义的。要想进一步优化程序的运行时间,只能忍痛舍弃虚函数机制。但是与此同时,又希望保留继承带来的其他便利性,此时就需要使用Curiously Recurring Template Prattern—奇异递归模板模式。
template <typename T>
class D {
public:
int num;
void base_print() { reinterpret_cast< T * const>(this)->print(); }
protected:
D() {}
};
class E :public D<E> {
public:
E(int i = 0) { num = i; }
void print() { cout << "I'm a E. my num=" << num << endl; }
void not_virtual_print() { cout << "not virtual func" << num << endl; }
};
int main()
{
E* e = new E(1);
e->print();
e->not_virtual_print();
delete e;
return 0;
}
对应的汇编代码变为:
e->print();
002C28A3 mov ecx,dword ptr [e]
002C28A6 call E::print (02C14ABh)
e->not_virtual_print();
002C28AB mov ecx,dword ptr [e]
002C28AE call E::not_virtual_print (02C14A1h)
这样调用e->print()的时候就不涉及虚函数机制了,直接当做类型E的成员函数调用。而基类中D的base_print()是用来保持多态特性的,之后会介绍。
可以看到CPU消耗减小了。递归模板的实现原理是这样的:基类D是模板,E继承了模板D的一个具体化类D<E>
。D<E>
一开始是不能完成具体化的,因为E还没有完成继承。所以顺序是E继承了void base_print()
(此时该函数中的T还没有具体化)->用E具体化D<E>
(此时void base_print()
中的T已经具体化为了E)->具体化E中的void base_print()
为reinterpret_cast< E * const>(this)->print();
。
保持多态特性
考虑如下的代码:
template <typename T>
class D {
public:
int num;
void base_print() { reinterpret_cast< T * const>(this)->print(); }
protected:
D() {}
};
class E :public D<E> {
public:
E(int i = 0) { num = i; }
void print() { cout << "I'm a E. my num=" << num << endl; }
void not_virtual_print() { cout << "not virtual func" << num << endl; }
};
class F :public D<F> {
public:
F(int i = 0) { num = i; }
void print() { cout << "I'm a F. my num=" << num << endl; }
void not_virtual_print() { cout << "not virtual func" << num << endl; }
};
template <typename T>
void print(T* d)
{
d->base_print();
}
int main()
{
E* e = new E(1);
F* f = new F(2);
e->base_print();
e->not_virtual_print();
print(e);
print(f);
delete e;
delete f;
return 0;
}
添加了新的模板函数print(),把多态的实现委托给它来实现,这样就能在编译期间确定模板函数print(),所以这就叫编译期多态,或者静态多态(static polymorphism)。缺点是对于每一个从D派生出来的类,都要具体化一个D<T>
和一个模板函数print(),这增加了代码的大小。所以到底是使用静态多态还是动态多态,需要编程人员根据实际情况权衡。
总结
动态多态可以在运行时确定派生类的信息,缺点是需要多次进行指针的解引用操作,消耗CPU。静态多态在编译期间就能确定派生类的信息,缺点是代码大小会变大。
关于动态多态的原理见我的另一篇文章:http://blog.youkuaiyun.com/popvip44/article/details/72763004