优秀的编程知识分享平台

网站首页 > 技术文章 正文

C++模板 - 21(静多态与泛型编程)

nanyue 2025-01-20 15:36:35 技术文章 3 ℃

多态(Polymorphism),将不同的特定行为关联到单一的通用符号,这种能力是面向对象编程的基石,在语言层面是使用类型继承+虚拟函数+指针访问,编译器则负责把vptr、vtable、RTTI等塞进去,在程序运行时通过动态绑定机制实现正确调用,所以也称为动多态

class Shape {
public:
  virtual void draw() = 0;
  virtual ~Shape() = default;
};

class Circle : public Shape {
public:
  void draw() override {}
};

class Rectangle : public Shape {
public:
  void draw() override {}
};

void draw_all(const std::vector<Shape *> &shapes) {
  for (auto *shape : shapes) {
    shape->draw();
  }
}

模板也同样具备将不同的特定行为关联到单一符号的能力,不过这种多态处理发生在编译期,我们称它为静多态

template <typename Drawable>
void draw_all(const std::vector<Drawable> &drawable_objs) {
  for (auto obj : drawable_objs) {
    obj.draw();
  }
}

从上面的代码来看,两种多态的差别还是很大的。

动多态基于某个抽象接口,这个抽象类需要明确定义(上面的Shape类定义),语义上非常清晰,同时通过基类指针,不同子类对象可以放置到同一个容器里,处理方式也显得更为优雅。缺点是间接访问的开销,执行效率略有损失。

静多态的语义约束看起来没有那么明显,上面的例子是要求类型有void draw()成员函数。另外你无法直接把具备draw成员函数的不同类型对象放置在同一个容器里(除非你通过某种代理类)。优点是多态在编译期绑定,运行性能好。

C++20引入了概念(Concepts),可以理解为静多态的某种语义接口(通过concept的定义),这样代码的表达能力就大大增强了,同时也减少了类型误用导致巨大篇幅的编译错误信息的产生。

template <typename T>
concept Drawable = requires(T a) {
  a.draw();
};

template <Drawable T> 
void draw_all(const std::vector<T> &drawable_objs) {
  for (auto obj : drawable_objs) {
    obj.draw();
  }
}

for循环正确的使用方式应该是auto&,这里使用传值语义只是想说明在静多态里不再需要通过指针或引用来使用通用接口了。

模板技术引入的静多态能力也给设计模式的带来了更多实现方式的选择,比如说桥接模式、策略模式等等,经典实现都是利用了动多态,通过基类指针屏蔽不同子类的类型差异。利用模板参数的静多态可以达到类似的实现效果,只是执行程序的尺寸比起动多态要大一些。

静多态的广泛应用形成了一种新的软件设计范式 - 范型编程。到底什么是范型程序设计,有很多不同的看法:使用模板参数将类型参数化可以看作是范型编程最直接的应用,但真正的范型设计应该涉及到多个模板参数在不同维度的组合,不同的类或函数依赖于其间的语义约束或概念,高效地实现各种功能。一个最直观的例子就是STL本身:各种底层不同数据结构的容器,针对容器数据的各种算法,连接其间的核心概念是它们的粘合剂 - 迭代器。容器提供自己的迭代器实现,算法利用各种迭代器的标准操作语义,最大效率地实现算法。

Tags:

最近发表
标签列表