18 模板和多态

18.1 动态多态

动态多态是使用同一种方法在运行时调用不同的代码,书中给出的是最为熟知的画图形的例子:

// poly/dynahier.hpp
#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;
        // ...
};
// ...
// poly/dynapoly.cpp
#include "dynahier.hpp"
#include <vector>

// 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<GeoObj*> const& elems)
{
    for (std::size_type i=0; i<elems.size(); ++i) {
        elems[i]->draw(); // 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<GeoObj*> coll;  // heterogeneous collection
    coll.push_back(&l);         // insert line
    coll.push_back(&c);         // insert circle
    drawElems(coll);            // draw different kinds of GeoObjs
    }

18.2 静态多态

使用模板可以得到上例的静态多态版本:

// poly/statichier.hpp
#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;
        // ...
};
// ...
// poly/staticpoly.cpp
#include "statichier.hpp"
#include <vector>

// draw any GeoObj
template<typename GeoObj>
void myDraw (GeoObj const& obj)
{
    obj.draw(); // call draw() according to type of object
}

// compute distance of center of gravity between two GeoObjs
template<typename GeoObj1, typename GeoObj2>
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<typename GeoObj>
void drawElems (std::vector<GeoObj> const& elems)
{
    for (unsigned i=0; i<elems.size(); ++i) {
        elems[i].draw(); // call draw() according to type of element
    }
}

int main()
{
    Line l;
    Circle c, c1, c2;

    myDraw(l);  // myDraw<Line>(GeoObj&) => Line::draw()
    myDraw(c);  // myDraw<Circle>(GeoObj&) => Circle::draw()

    distance(c1,c2);    // distance<Circle,Circle>(GeoObj1&,GeoObj2&)
    distance(l,c);      // distance<Line,Circle>(GeoObj1&,GeoObj2&)

    // std::vector<GeoObj*> coll;   // ERROR: no heterogeneous collection possible
    std::vector<Line> coll;         // OK: homogeneous collection possible
    coll.push_back(l);              // insert line
    drawElems(coll);                // draw all lines
}

18.3 动态多态和静态多态的对比

  • 术语

    • 通过继承实现的多态是绑定的(bounded)和动态的(dynamic)
      • 绑定是指参与多态的类型的接口由基类固定
      • 动态是指在运行时通过函数指针调用正确的函数
    • 通过模板实现的多态是未绑定的(unbounded)和静态的(static)
      • 未绑定是指参与多态的类型的接口是不固定的
      • 静态是指在编译时由编译器解析要调用的函数
  • 区别

    • 动态多态
      • 处理派生类集合的代码很优雅
      • 代码体积小
      • 代码可以被编译为二进制,源码可以不公开
    • 静态多态
      • 内置集合类型的处理很方便,但是类类型的通用接口不能通过统一的接口定义
      • 代码运行速度会快一些
      • 没有完整实现接口的类型也可以参与多态

18.4 Concept

Concept是指模板实参类型所需要支持的接口操作:

// poly/conceptsreq.hpp
#include "coord.hpp"

template<typename T>
concept GeoObj = requires(T x) {
    { x.draw() } -> void;
    { x.center_of_gravity() } -> Coord;
    // ...
};
// poly/conceptspoly.hpp
#include "conceptsreq.hpp"
#include <vector>

// draw any GeoObj
template<typename T>
requires GeoObj<T>
void myDraw (T const& obj)
{
    obj.draw(); // call draw() according to type of object
}

// compute distance of center of gravity between two GeoObjs
template<typename T1, typename T2>
requires GeoObj<T1> && GeoObj<T2>
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<typename T>
requires GeoObj<T>
void drawElems (std::vector<T> const& elems)
{
    for (std::size_type i=0; i<elems.size(); ++i) {
        elems[i].draw(); // call draw() according to type of element
    }
}

18.5 静态多态下的设计模式

在传统的设计模式中,桥接模式(bridge pattern)将接口与实现分离开。抽象类中包含一个实现类,抽象类通过该实现类提供具体的功能。可以简单理解为抽象类提供了一个功能,但是这个功能需要分若干步骤实现,实现类接口定义了每个步骤。

在动态多态下,抽象类中包含的是实现类的指针,而在静态多态下,抽象类中包含的是实现类的对象。

18.6 泛型编程

泛型编程是指用泛化的参数来为算法提供抽象的表达。原文:

Programming with generic parameters to finding the most abstract representation of efficient algorithms.

在C++中,泛型编程基本等价于模板编程,就像面向对象编程基本等价于使用虚函数一样。C++标准模板库(Standard Template Library,STL)是泛型编程的典型代表。STL中定义了算法和容器,容器是类,但是算法并不是容器类的成员函数,所以它们可以被应用于各种容器,并且用迭代器的类型进行了限制,这也是concept的一种体现。

// poly/printmax.cpp
#include <vector>
#include <list>
#include <algorithm>
#include <iostream>
#include "MyClass.hpp"

template<typename T>
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<MyClass> c1;
    std::list<MyClass> c2;
    // ...
    printMax(c1);
    printMax(c2);
}

18.7 后记

results matching ""

    No results matching ""