C++多态:深入CRTP,理解编译期的多态
来源:互联网 发布:最短路问题的算法例题 编辑:程序博客网 时间:2024/05/21 12:51
虚函数带来的额外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
- C++多态:深入CRTP,理解编译期的多态
- C++静态多态CRTP
- 深入理解Activity的生存期(二)
- 深入理解jvm十-早期(编译期)优化
- 深入理解 Java 虚拟机--早期(编译期)优化
- 深入理解JVM之早期(编译期)优化
- 了解隐式接口和编译期的多态(Effective C++_41)
- [深入理解Java虚拟机]第十章 程序编译与代码优化-早期(编译期)优化
- 通过几个简单的Linux命令,深入理解c语言编译的过程
- [深入理解Java虚拟机]第十一章 程序编译与代码优化-晚期(运行期)优化
- 【深入理解JVM】第10~13章 编译期优化、线程安全、锁优化 笔记
- 深入理解JVM(九)——早期(编译期)优化
- 深入理解.NET 的JIT编译方式
- 深入理解.NET 的JIT编译方式
- 深入理解.NET 的JIT编译方式
- 深入理解.NET 的JIT编译方式
- 深入理解Android工程的编译过程
- 深入理解Android工程的编译过程
- cipher工具彻底删除硬盘文件方法介绍
- docker简单操作
- MySQL的语句执行顺序
- protel四层板及内电层分割入门
- java servlet 几种页面跳转的方法
- C++多态:深入CRTP,理解编译期的多态
- VS2015---不允许 dllimport 静态数据成员的定义
- 【Python】python逆向入门
- Redis介绍以及安装(Linux)
- 打不开Anaconda Navigator:could not find or load the QT platform plugin "window in"简单解决方法
- 抓包工具之Charles
- XAMPP允许远程访问的方法
- SpringAOP处理通知中的参数
- 游戏开发广度与衍生玩法