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

阅读全文
0 0
原创粉丝点击