C++多态技能
当前位置:以往代写 > C/C++ 教程 >C++多态技能
2019-06-13

C++多态技能

C++多态技能

副标题#e#

摘要

本文描写了C++中的各类多态性。重点叙述了面向工具的动态多态和基于模板的静态多态,并劈头探讨了两种技能的团结利用。

要害词

多态 担任 虚函数 模板 宏 函数重载 泛型编程 泛型模式

导言

多态(polymorphism)一词最初来历于希腊语polumorphos,寄义是具有多种形式或形态的景象。在措施设计规模,一个遍及承认的界说是“一种将差异的非凡行为和单个泛化暗号相关联的本领”。和纯粹的面向工具措施设计语言差异,C++中的多态有着更遍及的寄义。除了常见的通过类担任和虚函数机制生效于运行期的动态多态(dynamic polymorphism)外,模板也答允将差异的非凡行为和单个泛化暗号相关联,由于这种关联处理惩罚于编译期而非运行期,因此被称为静态多态(static polymorphism)。

事实上,带变量的宏和函数重载机制也答允将差异的非凡行为和单个泛化暗号相关联。然而,习惯上我们并不将它们揭示出来的行为称为多态(或静态多态)。本日,当我们谈及多态时,假如没有明晰所指,默认就是动态多态,而静态多态则是指基于模板的多态。不外,在这篇以C++各类多态技能为主题的文章中,我们首先照旧回首一下C++社群争论已久的另一种“多态”:函数多态(function polymorphism),以及更不常提的“宏多态(macro polymorphism)”。

函数多态

也就是我们常说的函数重载(function overloading)。基于差异的参数列表,同一个函数名字可以指向差异的函数界说:

// overload_poly.cpp
#include <iostream>
#include <string>
// 界说两个重载函数
int my_add(int a, int b)
{
  return a + b;
}
int my_add(int a, std::string b)
{
  return a + atoi(b.c_str());
}
int main()
{
  int i = my_add(1, 2); // 两个整数相加
  int s = my_add(1, "2"); // 一个整数和一个字符串相加
  std::cout << "i = " << i << "\n";
  std::cout << "s = " << s << "\n";
}

按照参数列表的差异(范例、个数或兼而有之),my_add(1, 2)和my_add(1, "2")被别离编译为对my_add(int, int)和my_add(int, std::string)的挪用。实现道理在于编译器按照差异的参数列表对同名函数举办名字重整,尔后这些同名函数就酿成了互相差异的函数。例如说,也许某个编译器会将my_add()函数名字别离重整为my_add_int_int()和my_add_int_str()。


#p#副标题#e#

宏多态

带变量的宏可以实现一种低级形式的静态多态:

// macro_poly.cpp
#include <iostream>
#include <string>
// 界说泛化暗号:宏ADD
#define ADD(A, B) (A) + (B);
int main()
{
  int i1(1), i2(2);
  std::string s1("Hello, "), s2("world!");
  int i = ADD(i1, i2); // 两个整数相加
  std::string s = ADD(s1, s2); // 两个字符串“相加”
  std::cout << "i = " << i << "\n";
  std::cout << "s = " << s << "\n";
}

当措施被编译时,表达式ADD(i1, i2)和ADD(s1, s2)别离被替换为两个整数相加和两个字符串相加的详细表达式。整数相加浮现为求和,而字符串相加则浮现为毗连。措施的输出功效切合直觉:

1 + 2 = 3

Hello, + world! = Hello, world!

动态多态

这就是众所周知的的多态。现代面向工具语言对这个观念的界说是一致的。其技能基本在于担任机制和虚函数。譬喻,我们可以界说一个抽象基类Vehicle和两个派生于Vehicle的详细类Car和Airplane:

// dynamic_poly.h
#include <iostream>
// 民众抽象基类Vehicle
class Vehicle
{
public:
  virtual void run() const = 0;
};
// 派生于Vehicle的详细类Car
class Car: public Vehicle
{
public:
  virtual void run() const
  {
    std::cout << "run a car\n";
  }
};
// 派生于Vehicle的详细类Airplane
class Airplane: public Vehicle
{
public:
  virtual void run() const
  {
    std::cout << "run a airplane\n";
  }
};

#p#副标题#e#

客户措施可以通过指向基类Vehicle的指针(或引用)来哄骗详细工具。通过指向基类工具的指针(或引用)来挪用一个虚函数,会导致对被指向的详细工具之相应成员的挪用:

#p#分页标题#e#

// dynamic_poly_1.cpp
#include <iostream>
#include <vector>
#include "dynamic_poly.h"
// 通过指针run任何vehicle
void run_vehicle(const Vehicle* vehicle)
{
  vehicle->run(); // 按照vehicle的详细范例挪用对应的run()
}
int main()
{
  Car car;
  Airplane airplane;
  run_vehicle(&car); // 挪用Car::run()
  run_vehicle(&airplane); // 挪用Airplane::run()
}

此例中,要害的多态接口元素为虚函数run()。由于run_vehicle()的参数为指向基类Vehicle的指针,因而无法在编译期抉择利用哪一个版本的run()。在运行期,为了分配函数挪用,虚函数被挪用的谁人工具的完整动态范例将被会见。这样一来,对一个Car工具挪用run_vehicle(),实际大将挪用Car::run(),而对付Airplane工具而言将挪用Airplane::run()。

或者动态多态最吸引人之处在于处理惩罚异质工具荟萃的本领:

// dynamic_poly_2.cpp
#include <iostream>
#include <vector>
#include "dynamic_poly.h"
// run异质vehicles荟萃
void run_vehicles(const std::vector<Vehicle*>& vehicles)
{
  for (unsigned int i = 0; i < vehicles.size(); ++i)
  {
    vehicles[i]->run(); // 按照详细vehicle的范例挪用对应的run()
  }
}
int main()
{
  Car car;
  Airplane airplane;
  std::vector<Vehicle*> v; // 异质vehicles荟萃
  v.push_back(&car);
  v.push_back(&airplane);
  run_vehicles(v); // run差异范例的vehicles
}

在run_vehicles()中,vehicles[i]->run()依据正被迭代的元素的范例而挪用差异的成员函数。这从一个侧面浮现了面向工具编程气势气魄的优雅。

#p#副标题#e#

静态多态

假如说动态多态是通过虚函数来表达配合接口的话,那么静态多态则是通过“互相单独界说但支持配合操纵的详细类”来表达配合性,换句话说,必需存在必须的同名成员函数。

我们可以回收静态多态机制重写上一节的例子。这一次,我们不再界说vehicles类条理布局,相反,我们编写互相无关的详细类Car和Airplane(它们都有一个run()成员函数):

// static_poly.h
#include <iostream>
//详细类Car
class Car
{
public:
  void run() const
  {
    std::cout << "run a car\n";
  }
};
//详细类Airplane
class Airplane
{
public:
  void run() const
  {
    std::cout << "run a airplane\n";
  }
};

run_vehicle()应用措施被改写如下:


// static_poly_1.cpp
#include <iostream>
#include <vector>
#include "static_poly.h"
// 通过引用而run任何vehicle
template <typename Vehicle>
void run_vehicle(const Vehicle& vehicle)
{
  vehicle.run(); // 按照vehicle的详细范例挪用对应的run()
}
int main()
{
  Car car;
  Airplane airplane;
  run_vehicle(car); // 挪用Car::run()
  run_vehicle(airplane); // 挪用Airplane::run()
}

此刻Vehicle用作模板参数而非民众基类工具(事实上,这里的Vehicle只是一个切合直觉的暗号罢了,另外别无它意)。颠末编译器处理惩罚后,我们最终会获得run_vehicle<Car>()和 run_vehicle<Airplane>()两个差异的函数。这和动态多态差异,动态多态凭借虚函数分配机制在运行期只有一个run_vehicle()函数。

我们无法再透明地处理惩罚异质工具荟萃了,因为所有范例都必需在编译期予以抉择。不外,为差异的vehicles引入差异的荟萃只是举手之劳。由于无需再将荟萃元素范围于指针或引用,我们此刻可以从执行机能和范例安详两方面得到长处:

#p#副标题#e#

// static_poly_2.cpp
#include <iostream>
#include <vector>
#include "static_poly.h"
// run同质vehicles荟萃
template <typename Vehicle>
void run_vehicles(const std::vector<Vehicle>& vehicles)
{
  for (unsigned int i = 0; i < vehicles.size(); ++i)
  {
    vehicles[i].run(); // 按照vehicle的详细范例挪用相应的run()
  }
}
int main()
{
  Car car1, car2;
  Airplane airplane1, airplane2;
  std::vector<Car> vc; // 同质cars荟萃
  vc.push_back(car1);
  vc.push_back(car2);
  //vc.push_back(airplane1); // 错误:范例不匹配
  run_vehicles(vc); // run cars
  std::vector<Airplane> vs; // 同质airplanes荟萃
  vs.push_back(airplane1);
  vs.push_back(airplane2);
  //vs.push_back(car1); // 错误:范例不匹配
  run_vehicles(vs); // run airplanes
}

两种多态机制的团结利用

#p#分页标题#e#

在一些高级C++应用中,我们大概需要团结利用动态多态和静态多态两种机制,以期到达工具操纵的优雅、安详和高效。譬喻,我们既但愿一致而优雅地处理惩罚vehicles的run问题,又但愿“安详而高效”地完成给航行器(飞机、飞艇等)举办“空中加油”这样的高难度行动。为此,我们首先将上面的vehicles类条理布局改写如下:

// dscombine_poly.h
#include <iostream>
#include <vector>
// 民众抽象基类Vehicle
class Vehicle
{
  public:
  virtual void run() const = 0;
};
// 派生于Vehicle的详细类Car
class Car: public Vehicle
{
public:
  virtual void run() const
  {
    std::cout << "run a car\n";
  }
};
// 派生于Vehicle的详细类Airplane
class Airplane: public Vehicle
{
public:
  virtual void run() const
  {
    std::cout << "run a airplane\n";
  }
  void add_oil() const
  {
    std::cout << "add oil to airplane\n";
  }
};
// 派生于Vehicle的详细类Airship
class Airship: public Vehicle
{
public:
  virtual void run() const
  {
    std::cout << "run a airship\n";
  }
 
  void add_oil() const
  {
    std::cout << "add oil to airship\n";
  }
};

#p#副标题#e#

我们抱负中的应用措施可以编写如下:

// dscombine_poly.cpp
#include <iostream>
#include <vector>
#include "dscombine_poly.h"
// run异质vehicles荟萃
void run_vehicles(const std::vector<Vehicle*>& vehicles)
{
  for (unsigned int i = 0; i < vehicles.size(); ++i)
  {
    vehicles[i]->run(); // 按照详细的vehicle范例挪用对应的run()
  }
}
// 为某种特定的aircrafts同质工具荟萃举办“空中加油”
template <typename Aircraft>
void add_oil_to_aircrafts_in_the_sky(const std::vector<Aircraft>& aircrafts)
{
  for (unsigned int i = 0; i < aircrafts.size(); ++i)
  {
    aircrafts[i].add_oil();
  }
}
int main()
{
  Car car1, car2;
  Airplane airplane1, airplane2;
  Airship airship1, airship2;
  std::vector<Vehicle*> v; // 异质vehicles荟萃
  v.push_back(&car1);
  v.push_back(&airplane1);
  v.push_back(&airship1);
  run_vehicles(v); // run差异种类的vehicles
  std::vector<Airplane> vp; // 同质airplanes荟萃
  vp.push_back(airplane1);
  vp.push_back(airplane2);
  add_oil_to_aircrafts_in_the_sky(vp); // 为airplanes举办“空中加油”
  std::vector<Airship> vs; // 同质airships荟萃
  vs.push_back(airship1);
  vs.push_back(airship2);
  add_oil_to_aircrafts_in_the_sky(vs); // 为airships举办“空中加油”
}

我们保存了类条理布局,目标是为了可以或许操作run_vehicles()一致而优雅地处理惩罚异质工具荟萃vehicles的run问题。同时,操作函数模板add_oil_to_aircrafts_in_the_sky<Aircraft>(),我们仍然可以处理惩罚特定种类的vehicles — aircrafts(包罗airplanes和airships)的“空中加油”问题。个中,我们避开利用指针,从而在执行机能和范例安详两方面到达了预期方针。

结语

恒久以来,C++社群对付多态的内在和外延一直争论不休。在comp.object这样的网络论坛上,此类话题争论至今仍到处可见。曾经有人将动态多态(dynamic polymorphism)称为inclusion polymorphism,而将静态多态(static polymorphism)称为parametric polymorphism或parameterized polymorphism。

我留意到2003年斯坦福大学果真的一份C++ and Object-Oriented Programming教案中明晰提到了函数多态观念:Function overloading is also referred to as function polymorphism as it involves one function having many forms。文后的“参考文献”单位给出了这个网页链接。

#p#分页标题#e#

大概你是第一次看到宏多态(macro polymorphism)这个术语。不必讶异 — 也许我就是造出这个术语的“第一人”。显然,带变量的宏(或雷同于函数的宏或伪函数宏)的替换机制除了免去小型函数的挪用开销之外,也表示出了雷同的多态性。在我们上面的例子中,字符串相加所表示出来的切合直觉的毗连操纵,事实上是由底部运算符重载机制(operator overloading)支持的。值得指出的是,C++社群中有人将运算符重载所表示出来的多态称为ad hoc polymorphism。

David Vandevoorde和Nicolai M. Josuttis在他们的著作C++ Templates: The Complete Guide一书中系统地叙述了静态多态和动态多态技能。因为认为“和其他语言机制干系不大”,这本书没有提及“宏多态”(以及“函数多态”)。(需要说明的是,笔者本人是这本书的繁体中文版译者之一,本文正是基于这本书的第14章The Polymorphic Power of Templates编写而成)

动态多态只需要一个多态函数,生成的可执行代码尺寸较小,静态多态必需针对差异的范例发生差异的模板实体,尺寸会大一些,但生成的代码会更快,因为无需通过指针举办间接操纵。静态多态比动态多态越发范例安详,因为全部绑定都被查抄于编译期。正如前面例子所示,你不行将一个错误的范例的工具插入到从一个模板实例化而来的容器之中。另外,正如你已经看到的那样,动态多态可以优雅地处理惩罚异质工具荟萃,而静态多态可以用来实现安详、高效的同质工具荟萃操纵。

静态多态为C++带来了泛型编程(generic programming)的观念。泛型编程可以认为是“组件成果基于框架整体而设计”的模板编程。STL就是泛型编程的一个规范。STL是一个框架,它提供了大量的算法、容器和迭代器,全部以模板技能实现。从理论上讲,STL的成果虽然可以利用动态多态来实现,不外这样一来其机能必将大打折扣。

静态多态还为C++社群带来了泛型模式(generic patterns)的观念。理论上,每一个需要通过虚函数和类担任而支持的设计模式都可以操作基于模板的静态多态技能(甚至可以团结利用动态多态和静态多态两种技能)而实现。正如你看到的那样,Andrei Alexandrescu的天才作品Modern C++ Design: Generic Programming and Design Patterns Applied(Addison-Wesley)和Loki措施库已经走在了我们的前面。

    关键字:

在线提交作业