C++ 静态多态和动态多态 浅析

今天的C++已经是个多重泛型编程语言(multiparadigm programming lauguage),一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming)的语言。 这些能力和弹性使C++成为一个无可匹敌的工具,但也可能引发使用者的某些迷惑,比如多态。在这几种编程泛型中,面向对象编程、泛型编程以及很新的元编程形式都支持多态的概念,但又有所不同。
C++支持多种形式的多态,从表现的形式来看,有虚函数、模板、重载等,从绑定时间来看,可以分成静态多态和动态多态,也称为编译期多态和运行期多态。

本文即讲述这其中的异同。注意泛型编程和元编程通常都是以模板形式实现的,因此在本文中主要介绍基于面向对象的动态多态和基于模板编程的静态多态两种形式。另外其实宏也可以认为是实现静态多态的一种方式,实现原理就是全文替换,但C++语言本身就不喜欢宏,这里也忽略了“宏多态”。

什么是动态多态?

动态多态的设计思想:对于相关的对象类型,确定它们之间的一个共同功能集,然后在基类中,把这些共同的功能声明为多个公共的虚函数接口。各个子类重写这些虚函数,以完成具体的功能。客户端的代码(操作函数)通过指向基类的引用或指针来操作这些对象,对虚函数的调用会自动绑定到实际提供的子类对象上去。

从上面的定义也可以看出,由于有了虚函数,因此动态多态是在运行时完成的,也可以叫做运行期多态,这造就了动态多态机制在处理异质对象集合时的强大威力(当然,也有了一点点性能损失)。

看代码:

namespace DynamicPoly

{

class Geometry

{

public:

virtual void Draw()const = 0;

};

class Line : public Geometry

{

public:

virtual void Draw()const{    std::cout << "Line Draw()\n";    }

};

class Circle : public Geometry

{

public:

virtual void Draw()const{    std::cout << "Circle Draw()\n";    }

};

class Rectangle : public Geometry

{

public:

virtual void Draw()const{    std::cout << "Rectangle Draw()\n";    }

};

void DrawGeometry(const Geometry *geo)

{

geo->Draw();

}

//动态多态最吸引人之处在于处理异质对象集合的能力

void DrawGeometry(std::vector<DynamicPoly::Geometry*> vecGeo)

{

const size_t size = vecGeo.size();

for(size_t i = 0; i < size; ++i)

vecGeo[i]->Draw();

}

}

void test_dynamic_polymorphism()

{

DynamicPoly::Line line;

DynamicPoly::Circle circle;

DynamicPoly::Rectangle rect;

DynamicPoly::DrawGeometry(&circle);

std::vector<DynamicPoly::Geometry*> vec;

vec.push_back(&line);

vec.push_back(&circle);

vec.push_back(&rect);

DynamicPoly::DrawGeometry(vec);

}

动态多态本质上就是面向对象设计中的继承、多态的概念。动态多态中的接口是显式接口(虚函数),比如,

void DoSomething(Widget& w)

{

if( w.size() > 0 && w != someNastyWidget)

{

Widget temp(w);

temp.normalize();

temp.swap(w);

}

}

对于上面的代码,这要求:

由于w的类型被声明为Widget,所以w必须支持Widget接口,且通常可以在源码中找出这些接口(比如Widget.h),因此这些接口也就是显示接口;

Widget可能只是一个基类,他有子类,也就是说Widget的接口有可能是虚函数(比如上面的normalize),此时对接口的调用就表现出了运行时多态;

什么是静态多态?

静态多态的设计思想:对于相关的对象类型,直接实现它们各自的定义,不需要共有基类,甚至可以没有任何关系。只需要各个具体类的实现中要求相同的接口声明,这里的接口称之为隐式接口。客户端把操作这些对象的函数定义为模板,当需要操作什么类型的对象时,直接对模板指定该类型实参即可(或通过实参演绎获得)。

相对于面向对象编程中,以显式接口和运行期多态(虚函数)实现动态多态,在模板编程及泛型编程中,是以隐式接口和编译器多态来实现静态多态。

看代码:

namespace StaticPoly

{

class Line

{

public:

void Draw()const{    std::cout << "Line Draw()\n";    }

};

class Circle

{

public:

void Draw(const char* name=NULL)const{    std::cout << "Circle Draw()\n";    }

};

class Rectangle

{

public:

void Draw(int i = 0)const{    std::cout << "Rectangle Draw()\n";    }

};

template<typename Geometry>

void DrawGeometry(const Geometry& geo)

{

geo.Draw();

}

template<typename Geometry>

void DrawGeometry(std::vector<Geometry> vecGeo)

{

const size_t size = vecGeo.size();

for(size_t i = 0; i < size; ++i)

vecGeo[i].Draw();

}

}

void test_static_polymorphism()

{

StaticPoly::Line line;

StaticPoly::Circle circle;

StaticPoly::Rectangle rect;

StaticPoly::DrawGeometry(circle);

std::vector<StaticPoly::Line> vecLines;

StaticPoly::Line line2;

StaticPoly::Line line3;

vecLines.push_back(line);

vecLines.push_back(line2);

vecLines.push_back(line3);

//vecLines.push_back(&circle); //编译错误,已不再能够处理异质对象

//vecLines.push_back(&rect);    //编译错误,已不再能够处理异质对象

StaticPoly::DrawGeometry(vecLines);

std::vector<StaticPoly::Circle> vecCircles;

vecCircles.push_back(circle);

StaticPoly::DrawGeometry(circle);

}

静态多态本质上就是模板的具现化。静态多态中的接口调用也叫做隐式接口,相对于显示接口由函数的签名式(也就是函数名称、参数类型、返回类型)构成,隐式接口通常由有效表达式组成, 比如,

template<typename Widget,typename Other>

void DoSomething(Widget& w, const Other& someNasty)

{

if( w.size() > 0 && w != someNasty) //someNastyT可能是是T类型的某一实例,也可能不是

{

Widget temp(w);

temp.normalize();

temp.swap(w);

}

}

这看似要求:

类型T需要支持size、normalize、swap函数,copy构造函数,可以进行不等比较

类型T是在编译期模板进行具现化时才表现出调用不同的函数,此时对接口的调用就表现出了编译期时多态。

但是,

size函数并不需要返回一个整型值以和10比较,甚至都不需要返回一个数值类型,唯一的约束是它返回一个类型为X的对象,且X对象和int类型(数值10的类型)可以调用一个operator >,这个operator>也不一定非要一个X类型的参数不可,它可以通过隐式转换能将X类型转为Y类型对象,而只需要Y类型可以和int类型比较即可(好绕口,请看,这也侧面印证了模板编程编译错误很难解决)。

同样类型T并不需要支持operator!=,而只需要T可以转为X类型对象,someNastyT可以转为Y类型对象,而X和Y可以进行不等比较即可。

动态多态和静态多态的比较

静态多态

优点:

由于静多态是在编译期完成的,因此效率较高,编译器也可以进行优化;

有很强的适配性和松耦合性,比如可以通过偏特化、全特化来处理特殊类型;

最重要一点是静态多态通过模板编程为C++带来了泛型设计的概念,比如强大的STL库。

缺点:

由于是模板来实现静态多态,因此模板的不足也就是静多态的劣势,比如调试困难、编译耗时、代码膨胀、编译器支持的兼容性

不能够处理异质对象集合

动态多态

优点:

OO设计,对是客观世界的直觉认识;

实现与接口分离,可复用

处理同一继承体系下异质对象集合的强大威力

缺点:

运行期绑定,导致一定程度的运行时开销;

编译器无法对虚函数进行优化

笨重的类继承体系,对接口的修改影响整个类层次;

不同点:

本质不同,静态多态在编译期决定,由模板具现完成,而动态多态在运行期决定,由继承、虚函数实现;

动态多态中接口是显式的,以函数签名为中心,多态通过虚函数在运行期实现,静态多台中接口是隐式的,以有效表达式为中心,多态通过模板具现在编译期完成

相同点:

都能够实现多态性,静态多态/编译期多态、动态多态/运行期多态;

都能够使接口和实现相分离,一个是模板定义接口,类型参数定义实现,一个是基类虚函数定义接口,继承类负责实现;

附上本次测试的所有代码:

namespace DynamicPoly

{

class Geometry

{

public:

virtual void Draw()const = 0;

};

class Line : public Geometry

{

public:

virtual void Draw()const{    std::cout << "Line Draw()\n";    }

};

class Circle : public Geometry

{

public:

virtual void Draw()const{    std::cout << "Circle Draw()\n";    }

};

class Rectangle : public Geometry

{

public:

virtual void Draw()const{    std::cout << "Rectangle Draw()\n";    }

};

void DrawGeometry(const Geometry *geo)

{

geo->Draw();

}

//动态多态最吸引人之处在于处理异质对象集合的能力

void DrawGeometry(std::vector<DynamicPoly::Geometry*> vecGeo)

{

const size_t size = vecGeo.size();

for(size_t i = 0; i < size; ++i)

vecGeo[i]->Draw();

}

}

namespace StaticPoly

{

class Line

{

public:

void Draw()const{    std::cout << "Line Draw()\n";    }

};

class Circle

{

public:

void Draw(const char* name=NULL)const{    std::cout << "Circle Draw()\n";    }

};

class Rectangle

{

public:

void Draw(int i = 0)const{    std::cout << "Rectangle Draw()\n";    }

};

template<typename Geometry>

void DrawGeometry(const Geometry& geo)

{

geo.Draw();

}

template<typename Geometry>

void DrawGeometry(std::vector<Geometry> vecGeo)

{

const size_t size = vecGeo.size();

for(size_t i = 0; i < size; ++i)

vecGeo[i].Draw();

}

}

void test_dynamic_polymorphism()

{

DynamicPoly::Line line;

DynamicPoly::Circle circle;

DynamicPoly::Rectangle rect;

DynamicPoly::DrawGeometry(&circle);

std::vector<DynamicPoly::Geometry*> vec;

vec.push_back(&line);

vec.push_back(&circle);

vec.push_back(&rect);

DynamicPoly::DrawGeometry(vec);

}

void test_static_polymorphism()

{

StaticPoly::Line line;

StaticPoly::Circle circle;

StaticPoly::Rectangle rect;

StaticPoly::DrawGeometry(circle);

std::vector<StaticPoly::Line> vecLines;

StaticPoly::Line line2;

StaticPoly::Line line3;

vecLines.push_back(line);

vecLines.push_back(line2);

vecLines.push_back(line3);

//vecLines.push_back(&circle); //编译错误,已不再能够处理异质对象

//vecLines.push_back(&rect);    //编译错误,已不再能够处理异质对象

StaticPoly::DrawGeometry(vecLines);

std::vector<StaticPoly::Circle> vecCircles;

vecCircles.push_back(circle);

StaticPoly::DrawGeometry(circle);

}

/**无法编译通过,因此Widget要求有显式接口,但现在看不到*/

//void DoSomething(Widget& w)

//{

//    if( w.size() > 0 && w != someNastyWidget)

//    {

//        Widget temp(w);

//        temp.normalize();

//        temp.swap(w);

//    }

//}

/**可以编译通过,因此此处只是要求了只有在模板具现时需保证下面可编译(无调用,无具现)*/

template<typename Widget,typename Other>

void DoSomething(Widget& w, const Other& someNasty)

{

if( w.size() > 0 && w != someNasty) //someNastyT可能是是T类型的某一实例,也可能不是

{

Widget temp(w);

temp.normalize();

temp.swap(w);

}

}

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-24 16:11:18

C++ 静态多态和动态多态 浅析的相关文章

静态多态和动态多态

多态 多态就是多种形态,C++的多态分为静态多态和动态多态. 静态多态就是重载,因为是在编译期决议确定,所以称为静态多态. 动态多态就是通过继承重写基类的虚函数实现的多态,因为是在运行时决议确定,所以称为动态多态. class Base { public : virtual void func1() { cout<<"Base::func1" <<endl; } virtual void func2() { cout<<"Base::fun

polymorphism-多态(函数多态、宏多态、静态多态、动态多态)

多态(polymorphism) 字面上意思--多种形态,即同一种方法的行为随上下文而异. 维基百科:Polymorphism (computer science), the ability incomputer programming to present the same interface for differing underlyingforms (data types). 1.函数多态(function polymorphism):也即是函数重载(functionoverloading

静态多态与动态多态

面向对象编程的多态从绑定时间来看,可以分成静态多态和动态多态,也称为编译期多态和运行期多态. java中overload是静态多态,即根据参数列表进行最佳匹配,在编译阶段决定要具体执行哪个方法.而与之相反,overriden methods则是在run-time进行动态检查. 举例说明: public class UseAnimals { public void doStuff(Animal a) { System.out.println("Animal"); } public voi

C/C++ 静态多态与动态多态

静态多态就是在系统编译期间就可以确定程序执行到这里将要执行哪个函数,比如函数的重载. 动态多态则是利用虚函数实现了运行时的多态,也就是说在系统编译的时候并不知道程序将要调用哪一个函数,只有在运行到这里的时候才能确定接下来会跳转到哪一个函数的栈帧. 虚函数就是在基类中声明该函数是虚拟的(在函数之前加virtual关键字),然后在子类中正式的定义(子类中的该函数的函数名,返回值,函数参数个数,参数类型,全都与基类的所声明的虚函数相同,此时才能称为重写,才符合虚函数,否则就是函数的重载),再定义一个指

OC多态,动态类型绑定

// //  main.m //  OC7类 // //  Created by Zoujie on 15/8/23. //  Copyright (c) 2015年 Zoujie. All rights reserved. // #import <Foundation/Foundation.h> #import "Fraction.h"//导入头文件 #import "Complex.h" #define Choose  0 int main(int 

分析多继承下的动态多态。

一.首先我们先了解一下三个概念: 1.重载.2.隐藏.3.覆盖(重写) 如何实现重载?--2个条件: 1-在同一作用域内. 2-两个函数函数名相同,参数不同,返回值可以不同. 此时两个函数就实现了重载,当然这是C++对于C特有的,因为C的时候对参数并没有太多的考虑,C++的编译器在编译时对函数进行了重命名,所以就算是函数名相同的函数,如果参数不同,就会是不同的函数,对应不同的情况. 如何实现隐藏/重定义?--2个条件: 1-在不同作用域下,大多在继承上体现. 2-函数名相同即可. 例如在 B类公

浅谈为什么只有指针能够完成多态及动态转型的一个误区

c++多态由一个函数地址数组Vtable和一个指向Vtable的指针vptr实现. 具体来说,类拥有自己的vtable,类的vtable在编译时刻完成. 每个对象有自己的vptr指针,该指针初始化时指向对象所实现的类的vtable. 关于向上转型的误区: 通常对于向上转型的理解是这样的,当子类对象向上转型(允许隐式)成父类对象时,实际上只是将子类对象暂时看做父类对象,内部的数据并未改变. 对于没有虚函数的对象,这句话是正确的,但是,当引入虚函数后,这样的理解是有问题的,实际上,向上转型的过程中,

动态多态

public interface Ipower { public abstract void tigongdianyuan(); } 动态多态:指系统A访问系统B的服务时,系统B可以通过多种实现来提供服务,而这一切对于A来说都是透明的 public class ACPower implements Ipower { public void tigongdianyuan() { System.out.println("适配器提供电源"); } } public class Barry i

第一次接触OC多态,动态类型与动态绑定

多态:允许不同的类定义相同的方法. 动态类型:程序直到执行时才能确定所属的类. 动态绑定:程序直到执行时才能确定实际要调用的方法. id类型:一种通用的对象类型,也就是说,id可以用来存储属于任何类的对象,让不同类的输出不用特定设定,通用一个id类型就好. 多态的出现时为了让不同的类能使用同明的方法.比如add,这个让程序的可读性大大提高,也降低了编程难度.add就是+,哪种类型的+都用add标明方法,清晰易懂. 动态类型与动态绑定是为了解决随多态的便利而引起的弊端,有了动态类型与动态绑定,不用