CPP-Templates-2nd--第十八章 模板的多态性

目录

18.1 动态多态(dynamic polymorphism)

18.2 静态多态

18.3 动态多态 VS 静态多态

术语

优点和缺点

18.4 使用 concepts

18.5 新形势的设计模式

18.6 泛型编程(Generic Programming)


参考:GitHub - Walton1128/CPP-Templates-2nd--: 《C++ Templates 第二版》中文翻译,和原书排版一致,第一部分(1至11章)以及第18,19,20,21、22、23、24、25章已完成,其余内容逐步更新中。 个人爱好,发现错误请指正

多态也是面向对象编程范式 的基石,在 C++中它主要由继承和虚函数实现。由于这一机制主要(至少是一部分)在运行 期间起作用,因此我们称之为动态多态(dynamic polymorphism)。它也是我们通常在讨论 C++中的简单多态时所指的多态。但是,模板也允许我们用单个统一符号将不同的特定行为 关联起来,不过该关联主要发生在编译期间,我们称之为静态多态(static polymorphism)。 

18.1 动态多态(dynamic polymorphism)

#include "coord.hpp"
// common abstract base class GeoObj for geometric objects
class GeoObj {
public:
// draw geometric object:
virtual void draw() const = 0;
// return center of gravity of geometric object:
virtual Coord center_of_gravity() const = 0; …
virtual ~GeoObj() = default;
};
// concrete geometric object class Circle
// - derived from GeoObj
class Circle : public GeoObj {
public:
virtual void draw() const override;
virtual Coord center_of_gravity() const override; …
};
// concrete geometric object class Line
// - derived from GeoObj
class Line : public GeoObj {
public:
virtual void draw() const override;
virtual Coord center_of_gravity() const override; …
};

在我们的例子中,具体的代码可以被简写成这样:

#include "dynahier.hpp"
#include 
// draw any GeoObj
void myDraw (GeoObj const& obj)
{
obj.draw(); // call draw() according to type of object
}
// compute distance of center of gravity between two GeoObjs
Coord distance (GeoObj const& x1, GeoObj const& x2)
{
Coord c = x1.center_of_gravity() - x2.center_of_gravity();
return c.abs(); // return coordinates as absolute values
}
// draw heterogeneous collection of GeoObjs
void drawElems (std::vector const& elems)
{
for (std::size_type i=0; idraw(); // call draw() according to type of element
}
}
int main(){
Line l;
Circle c, c1, c2;
myDraw(l); // myDraw(GeoObj&) => Line::draw()
myDraw(c); // myDraw(GeoObj&) => Circle::draw()
distance(c1,c2); // distance(GeoObj&,GeoObj&)
distance(l,c); // distance(GeoObj&,GeoObj&)
std::vector coll; // heterogeneous collection
coll.push_back(&l); // insert line
coll.push_back(&c); // insert circle
drawElems(coll); // draw different kinds of GeoObjs
}

 在编译期间并不能知道将要被真正调用 的函数。但是,在运行期间,则会基于各个对象的完整类型来决定将要调用的函数。因此, 取决于集合对象的真正类型,适当的操作将会被执行:如果 mydraw()处理的是 Line 的对象, 表 达 式 obj.draw() 将 调 用 Line::draw() , 如 果 处 理 的 是 Circle 的 对 象 , 那 么 就 会 调 用 Circle::draw()。类似地,对于 distance(),调用的也将是与参数对象对应的 center_of_gravity()。

能够处理异质集合中不同类型的对象,或许是动态多态最吸引人的特性。这一概念在 drawElems()函数中得到了体现:

表达式 elems[i]->draw()

会调用不同的成员函数,具体情况取决于元素的动态类型。

18.2 静态多态

模板也可以被用来实现多态。不同的是,它们不依赖于对基类中公共行为的分解。取而代之 的是,这一“共性(commonality)”隐式地要求不同的“形状(shapes)”必须支持使用 了相同语法的操作(比如,相关函数的名字必须相同)。

在定义上,具体的 class 之间彼此 相互独立(参见 18.2)。在用这些具体的 class 去实例化模板的时候,这一多态能力得以实 现。

比如,上一节中的 myDraw():

void myDraw (GeoObj const& obj) // GeoObj is abstract base
class
{
obj.draw();
}

也可以被实现成下面这样:

template
void myDraw (GeoObj const& obj) // GeoObj is template parameter
{
obj.draw();
}

比较 myDraw()的两种实现,可以发现其主要的区别是将 GeoObj 用作模板参数而不是公共基 类。但是,在表象之下还有很多区别。比如,使用动态多态的话,在运行期间只有一个 myDraw() 函数,但是在使用模板的情况下,却会有多种不同的函数,例如 myDraw()和 myDraw()。

#include "coord.hpp"
// concrete geometric object class Circle
// - not derived from any class
class Circle {
public:
void draw() const;
Coord center_of_gravity() const;
…
};
// concrete geometric object class Line
// - not derived from any class
class Line {
public:
void draw() const;
Coord center_of_gravity() const; …
};

现在,可以像下面这样使用这些类:

#include "statichier.hpp"
#include 
// draw any GeoObj
template
void myDraw (GeoObj const& obj)
{
obj.draw(); // call draw() according to type of object
}
// compute distance of center of gravity between two GeoObjs
template
Coord distance (GeoObj1 const& x1, GeoObj2 const& x2)
{
Coord c = x1.center_of_gravity() - x2.center_of_gravity();
return c.abs(); // return coordinates as absolute values
}
// draw homogeneous collection of GeoObjs
template
void drawElems (std::vector const& elems)
{
for (unsigned i=0; i(GeoObj&) => Line::draw()
myDraw(c); // myDraw(GeoObj&) =>
Circle::draw()
distance(c1,c2); //distance
(GeoObj1&,GeoObj2&)
distance(l,c); // distance(GeoObj1&,GeoObj2&)
// std::vector coll; //ERROR: no heterogeneous
collection possible
std::vector coll; // OK: homogeneous collection
possible
coll.push_back(l); // insert line
drawElems(coll); // draw all lines
}

但是使用这种方式,我们将不再能够透明地处理异质容器。这也正是 static 多态中的 static 部分带来的限制:所有的类型必须在编译期可知。不过,我们可以很容易的为不同的集合对 象类型引入不同的集合。这样就不再要求集合的元素必须是指针类型,这对程序性能和类型 安全都会有帮助。

18.3 动态多态 VS 静态多态

术语

Static 和 dynamic 多态提供了对不同 C++编程术语的支持:

 通过继承实现的多态是有界的(bounded)和动态的(dynamic):

         有界的意思是,在设计公共基类的时候,参与到多态行为中的类型的相关接口就已 经确定(该概念的其它一些术语是侵入的(invasive 和 intrusive))。

         动态的意思是,接口的绑定是在运行期间执行的。

 通过模板实现的多态是无界的(unbounded)和静态的(static):

         无界的意思是,参与到多态行为中的类型的相关接口是不可预先确定的(该概念的 其它一些术语是非侵入的(noninvasive 和 nonintrusive))

         静态的意思是,接口的绑定是在编译期间执行的

优点和缺点

C++中的动态多态有如下优点:

 可以很优雅的处理异质集合。

 可执行文件的大小可能会比较小(因为它只需要一个多态函数,不像静态多态那样,需 要为不同的类型进行各自的实例化)。

 代码可以被完整的编译;因此没有必须要被公开的代码(在发布模板库时通常需要发布 模板的源代码实现)。

作为对比,下面这些可以说是 C++中 static 多态的优点:

 内置类型的集合可以被很容易的实现。更通俗地说,接口的公共性不需要通过公共基类 实现。  产生的代码可能会更快(因为不需要通过指针进行重定向,先验的(priori)非虚函数 通常也更容易被 inline)。

 即使某个具体类型只提供了部分的接口,也可以用于静态多态,只要不会用到那些没有 被实现的接口即可。

通常认为静态多态要比动态多态更类型安全(type safe),因为其所有的绑定都在编译期间 进行了检查。

18.4 使用 concepts

        C++语言的设计者们一直在致力于实现一种能够为模板参数显式地提供(或 者是检查)接口的能力。在 C++中这一接口被称为 concept。它代表了为了能够成功的实例 化模板,模板参数必须要满足的一组约束条件。

Concept 可以被理解成静态多态的一类“接口”。在我们的例子中,可能会像下面这样:

#include "coord.hpp"
template
concept GeoObj = requires(T x) {
{ x.draw() } -> void;
{ x.center_of_gravity() } -> Coord; …
};

在这里我们使用关键字 concept 定义了一个 GeoObj concept,它要求一个类型要有可被调用 的成员函数 draw()和 center_of_gravity(),同时也对它们的返回类型做了限制。

#include "conceptsreq.hpp"
#include 
// draw any GeoObj
template
requires GeoObj
void myDraw (T const& obj)
{
obj.draw(); // call draw() according to type of object
}
// compute distance of center of gravity between two GeoObjs
template
requires GeoObj && GeoObj
Coord distance (T1 const& x1, T2 const& x2)
{
Coord c = x1.center_of_gravity() - x2.center_of_gravity();
return c.abs(); // return coordinates as absolute values
}
// draw homogeneous collection of GeoObjs
template
requires GeoObj
void drawElems (std::vector const& elems)
{
for (std::size_type i=0; i

18.5 新形势的设计模式

18.6 泛型编程(Generic Programming)

在 C++的语境中,泛型编程有时候也被定义成模板编程(而面向对象编程被认为是基于虚函 数的编程)。在这个意义上,几乎任何 C++模板的使用都可以被看作泛型编程的实例。但是, 开发者通常认为泛型编程还应包含如下这一额外的要素:

该模板必须被定义于一个框架中,且必须能够适用于大量的、有用的组合。

我们可以在不知道元素的具体存储方式的情况下,实现一种求取序列中元素最大值 的方法:

template
Iterator max_element (Iterator beg, //refers to start of collection
Iterator end) //refers to end of collection
{
// use only certain Iterator operations to traverse all elements
// of the collection to find the element with the maximum value
// and return its position as Iterator …
}

这样就可以不用去给所有的线性容器都提供一些诸如 max_element()的操作,容器本身只 要提供一个能够遍历序列中数值的迭代器类型,以及一些能够创建这些迭代器的成员函数就 可以了:

namespace std {
template
class vector {
public:
using const_iterator = …; // implementation-specific iterator … // type for constantvectors
const_iterator begin() const; // iterator for start of
collection
const_iterator end() const; // iterator for end of collection …
};
template
class list {
public:
using const_iterator = …; // implementation-specific iterator … // type for constant lists
const_iterator begin() const; // iterator for start of
collection
const_iterator end() const; // iterator for end of
collection …
};
}

现在就可以通过调用泛型操作 max_element()(以容器的 beginning 和 end 伟参数)来寻找任 意集合中的最大值了(省略了对空集合的处理):

#include 
#include 
#include 
#include 
#include "MyClass.hpp"
template
void printMax (T const& coll){
// compute position of maximum value
auto pos = std::max_element(coll.begin(),coll.end());
// print value of maximum element of coll (if any):
if (pos != coll.end()) {
std::cout << *pos << ’\n’;
}
else {
std::cout << "empty" << ’\n’;
}
}
int main()
{
std::vector c1;
std::list c2; …
printMax(c1);
printMax(c2);
}

通过用这些迭代器来参数化其操作,STL 避免了相关操作在定义上的爆炸式增长。我们并没 有为每一种容器都把每一种操作定义一遍,而是只为一个算法进行一次定义,然后将其用于 所有的容器。泛型的关键是迭代器,它由容器提供并被算法使用。这样之所以可行,是因为 迭代器提供了特定的、可以被算法使用的接口。这些接口通常被称为 concept,它代表了为 了融入该框架,模板必须满足的一组限制条件。此外,该概念还可用于其它一些操作和数据 结构。

原则上,类似于 STL 方法的一类功能都可以用动态多态实现。但是在实际中,由于迭代器的 concept 相比于虚函数的调用过于轻量级,因此多态这一方法的用途有限。基于虚函数添加 一个接口层,很可能会将我们的操作性能降低一个数量级(甚至更多)。

你可能感兴趣的:(开发语言,c++)