C++多态:深入CRTP,理解编译期的多态

xiaoxiao2021-02-28  108

虚函数带来的额外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.csdn.net/popvip44/article/details/72763004

转载请注明原文地址: https://www.6miu.com/read-70162.html

最新回复(0)