说到面向对象特性之一“多态”,以我的水平已经说不出太多新意了。相信很多程序员代码K多了,做梦都在“多态中”运行着。常规的多态是C++语义内置支持的一种特性,通过虚函数可以实现这个特性,为了后面以示区别,我们姑且把这种多态称为“动态多态”或”运行期多态“,而本文总主要想讨论下“静态多态”,也可以叫“编译期多态”,同时一起来看下,静态多态会给我们带来哪些惊喜之处,拭目以待吧。

首先看个正常通过虚函数实现多态的常规例子,如下所示,很简单明了无需多言。

 
#include <iostream>
#include <string> class BasicClassic
{
public:
virtual void Print() = 0;
}; class DerivedClassic1 : public BasicClassic
{
public:
DerivedClassic1() {} virtual void Print() {
std::cout << "DerivedClassic1 Print" << std::endl;
}
}; class DerivedClassic2 : public BasicClassic
{
public:
DerivedClassic2() {} virtual void Print() {
std::cout << "DerivedClassic2 Print" << std::endl;
}
};
通过虚函数,在运行时通过虚函数表指针,通过索引找到对应函数,然后进行调用,所以前面我们称之为动态多态或运行时多态。那静态多态又是怎么回事,如何实现呢?答案是模版。如果熟悉COM的同学应该见过框架中很多通过模版来实现多态的实现,看下如下的实现:
 
template<typename Derived>
class Basic
{
public:
inline void Print() {
SelfCast()->Print();
} protected:
inline Derived* SelfCast() {
return static_cast <Derived*>(this);
}
};
class Derived1 : public Basic<Derived1>
{
public:
Derived1() {} inline void Print() {
std::cout << "Derived1 Print" << std::endl;
}
};
class Derived2 : public Basic<Derived2>
{
public :
Derived2() {} inline void Print() {
std::cout << "Derived2 Print" << std::endl;
} static std::string Name() {
return "Derived2 Class" ;
}
};
 
具体使用的代码:
 
Basic<Derived1>* der1 = new Derived1();
der1->Print();
Basic<Derived2>* der2 = new Derived2();
der2->Print();
 
输出结果:
Derived1 Print
Derived2 Print
这里实现的关键是SelfCast函数,通过static_cast把当前对象强制转换为具体指定的子类对象,这里是Derived1。其实实现的原理很简单,不难理解,我们需要重点讨论的是,这么样实现的多态跟常规虚函数的做法到底有什么不同,有什么优势?

大家应该都知道,虚函数的使用会带来额外的开销,具有虚函数的class类型都需要一张虚函数表,而每多一个虚函数,对应类型的对象的大小就会增加4bytes(32位机器下),夸张的试想一下如果有10个父类,每个父类都有100个虚函数的情况下,每个对象会增加多少?
4x10x100=4000bytes!
除了空间上的开销,每个虚函数的调用在时间上都会比普通函数多一次整形加法和一次指针间接引用,也就是时间上的开销
这种开销虽然在绝大多数的应用中都是可以忽略不计的,但是总会存在一些对性能与开销无比在意的关键代码。根据28法则,应用中80%的时间都是在运行其中20%的代码,那么有时候对这20%代码的优化也许会带来显著的改善。
回到正题,我们把传统的实现方式称为动态多态,而模板方式的实现则是静态多态,归纳下他们的区别:
  1. 动态多态的多态性是在运行期决定的,而静态多态是在编译期就决定的
  2. 动态多态的实现需要更多空间上的开销,每个对象会因为一个虚函数而增加4bytes,静态多态则没有这个问题
  3. 动态多态的实现需要更多的时间开销,虚函数的调用在时间上都会比普通函数多一次整形加法和一次指针间接引用,静态多态中的调用则跟普通函数的调用开销相同
  4. 动态多态(虚函数)是C++编译器内置支持的一种实现方式,而静态多态则会额外带来一些使用的复杂性
  5. 动态多态中虚函数不能通过内联来优化执行效率,而静态多态中则可以通过内联来进一步优化函数执行效率

综上所述,在实际使用中,到底选择哪种实现方式,要因需而异,如果没有特别的性能需求时,完全没有必要为了写的更酷而去使用模版的方式来实现,反而得不偿失,但如果针对特别需求或关键性能的代码,则可以考虑这种优化。

另外再看一种使用方式,模版还可以实现static函数的类似多态特性,如下所示:
 
template <typename Derived>
class Basic
{
public :
Basic() { } inline void Print() {
std::cout << Basic<Derived>::Name() << std::endl;
SelfCast()->Print();
} static std::string Name() {
return Derived::Name();
} protected :
inline Derived* SelfCast() {
return static_cast <Derived*>( this);
}
}; class Derived1 : public Basic<Derived1>
{
public :
Derived1() {} inline void Print() {
std::cout << "Derived1 Print" << std::endl;
} static std::string Name() {
return "Derived1 Class" ;
}
};
 
也就是说针对某些希望定义为static的函数,当你希望能在基类抽象方法中根据当前对象具体类型来使用子类相应static函数时,如上方法可以达成你的目的,这未尝不是一种不错的实现方式。
ok,静态多态就说到这,延伸还有哪些特定的应用,欢迎大家一起讨论。

【C++模版之旅】静态多态的讨论的更多相关文章

  1. 【C++模版之旅】项目中一次活用C++模板(traits)的经历 -新注解

    问题与需求: 请读者先看这篇文章,[C++模版之旅]项目中一次活用C++模板(traits)的经历. 对于此篇文章提出的问题,我给出一个新的思路. talking is cheap,show me t ...

  2. C++中的静态多态和动态多态

    C++中的静态多态和动态多态 今天的C++已经是个多重泛型编程语言(multiparadigm programming lauguage),一个同时支持过程形式(procedural).面向对象形式( ...

  3. C++进阶--静态多态

    //############################################################################ /* * 多态 */ // 常见的动态多态 ...

  4. c++ 宏多态 动态多态和静态多态(转载)

    转载出处:通道 多态(polymorphism)一词最初来源于希腊语polumorphos,含义是具有多种形式或形态的情形.在程序设计领域,一个广泛认可的定义是“一种将不同的特殊行为和单个泛化记号相关 ...

  5. C++ //多态 //静态多态:函数重载 和 运算符重载 属于静态多态 ,复用函数名 //动态多态:派生类和虚函数实现运行时多态

    1 //多态 2 //静态多态:函数重载 和 运算符重载 属于静态多态 ,复用函数名 3 //动态多态:派生类和虚函数实现运行时多态 4 5 //静态多态和动态多态的区别 6 //静态多态的函数地址早 ...

  6. c++ 静态多态与动态多态

    多态polymorphism是指具有多种形态的情况,它能根据单一的标记关联不同的行为.多态是面向对象程序设计的基础.在面向对象程序设计中的多态是一种运行时的多态.C++中有两种多态,称为动多态(运行时 ...

  7. 【C++模版之旅】项目中一次活用C++模板(traits)的经历

    曾经曾在一个项目中碰到过一个挺简单的问题,但一时又不能用普通常规的方法去非常好的解决,最后通过C++模板的活用,通过traits相对照较巧妙的攻克了这个问题.本文主要想重现问题发生,若干解决方式的比較 ...

  8. Android逆向之旅---静态方式分析破解视频编辑应用「Vue」水印问题

    一.故事背景 现在很多人都喜欢玩文艺,特别是我身边的UI们,拍照一分钟修图半小时.就是为了能够在朋友圈显得逼格高,不过的确是挺好看的,修图的软件太多了就不多说了,而且一般都没有水印啥的.相比较短视频有 ...

  9. c++ 多态,虚函数、重载函数、模版函数

    c++三大特性:封装.继承.多态.封装使代码模块化,继承扩展已存在的代码,多态的目的是为了接口重用 虚函数实现:虚函数表:指针放到虚函数表 多态:同名函数对应到不同的实现 构造父类指针指向子类的对象 ...

随机推荐

  1. JAVA 并发实现六(Volatile的使用)

    Java™ 语言包含两种内在的同步机制:同步块(或方法)和 volatile 变量. 这两种机制的提出都是为了实现代码线程的安全性.其中 Volatile 变量的同步性较差(但有时它更简单并且开销更低 ...

  2. mysql命令行里的加载更多显示

    mysql> pager morePAGER set to 'more'mysql> pager lessPAGER set to 'less'mysql> nopagerPAGER ...

  3. javascript中子类如何继承父类

    参考阮一峰的文章:http://javascript.ruanyifeng.com/oop/inheritance.html#toc4 function Shape() { this.x = 0; t ...

  4. 【自由谈】城域网IPv6过渡技术——4v6场景技术总结(1)

    为什么会存在4v6应用场景?主要是从“云-管-端”的IPv6状态决定的,“云”侧IPv4类业务丰富,IPv6驱动力小,所以“云”在较长一段时间内还是以IPv4类业务为主.“管”侧的IPv6化程度高,设 ...

  5. [React] Extracting Private React Components

    we leverage private components to break our render function into more manageable pieces without leak ...

  6. [React Testing] Redux Reducers

    Sometimes we want to test our Redux reducers to make sure they work as expected. In this lesson we w ...

  7. SELinux 与强制访问控制系统

    SELinux 全称 Security Enhanced Linux (安全强化 Linux),是 MAC (Mandatory Access Control,强制访问控制系统)的一个实现,目的在于明 ...

  8. C#基础枚举的设计

    枚举分为:简单枚举和标记枚举(为了枚举值位操作) 标记枚举的用法:

  9. 关于在css里设置图片圆角的问题

    今天做了一个项目,效果图内页的产品图片都是带圆角的,于是前端的做了圆角的效果,div+css是这样的,首先div布局是: <div class="tiandi_item" o ...

  10. Multipatch对象

    Multipatch对象是 TriangleStrip 和TriangleFan, Trangle,Ring对象的集合 TriangleStrip TriangleFan Trangle