多态性也是面向对象泛型编程的基础,C++ 中通过类继承和虚函数来支持多态。因为这些机制 (至少部分) 在运行时处理,所以这里讨论动态多态性。通常所说的多态性,指的就是动态多态性 。 然而,模板还允许将不同的特定行为与单个泛型表示关联起来,但这种关联通常在编译时进行处理, 称之为静态多态性。
静态多态
静态多态优势:
- 编译时多态:静态多态的实现主要依靠模板(Template)技术,编译器在编译阶段就能确定函数或类的具体实现,通过模板参数推导和实例化来生成特定类型的代码。
- 性能优势:由于编译时就确定了调用哪一个函数版本,所以无需运行时的类型检查和虚函数表查找,理论上可以获得更好的运行时性能。
- 类型安全性:编译器会在编译阶段检查模板的正确性,错误会在编译阶段捕获,这增强了类型的安全性。
- 不受继承关系限制:静态多态不需要类之间存在继承关系,只要满足模板参数的要求,就能实现多态性。
- 灵活性:静态多态允许对内置类型和自定义类型进行同样的处理,不需要类之间共享公共基类。
但是在涉及容器(集合)时,如果要存放不同类型(异构)的对象,如一个集合中同时包含圆形和线段等不同类型的几何对象,静态多态性就显得力不从心。因为在编译时模板参数就必须是确定的类型,不能容纳未知的多种类型。
cpp
#include <iostream>
#include <vector>
class Circle {
public:
int x,y; // 为了体现和Line异构
void doDraw() const {
std::cout<<"Circle::doDraw()"<<std::endl;
}
};
class Line {
public:
int len; // 为了体现和Circle异构
void doDraw() const{
std::cout<<"Line::doDraw()"<<std::endl;
}
};
template<typename T>
void draw(T const& t) {
t.doDraw();
}
template<typename T>
void drawAll(std::vector<T> const& v) {
for(auto const& e:v) {
e.doDraw();
}
}
int main() {
Circle c{};
draw(c);
Line l{};
draw(l);
// 但静态多态的一个局限,就是没有办法在容器中存放异构类型的元素
std::vector<Line> vc{Line{},Line{},Line{}};
drawAll(vc);
return 0;
}
C++17提供了一个新特性:std::variant , 可以在一定程度上弥补静态多态在面对容器容纳异构类型的不足:
cpp
#include <iostream>
#include <variant>
#include <vector>
class Circle {
public:
int x,y; // 为了体现和Line异构
void doDraw() const {
std::cout<<"Circle::doDraw()"<<std::endl;
}
};
class Line {
public:
int len; // 为了体现和Circle异构
void doDraw() const{
std::cout<<"Line::doDraw()"<<std::endl;
}
};
template<typename T>
void draw(T const& t) {
t.doDraw();
}
template<typename T>
void drawAll(std::vector<T> const& v) {
for(auto const& e:v) {
// 使用auto推断e的类型,然后输出
std::visit([](const auto& arg) { arg.doDraw(); }, e);
}
}
int main() {
using Shape = std::variant <Circle,Line>; // 使用variant来解决异构类型在容器中的问题
std::vector<Shape> vd{Line{}, Circle{}, Line{}};
drawAll(vd);
return 0;
}
静态多态性通常认为比动态多态性更类型安全,因为所有绑定都在编译时检查。例如,模板实 例化的容器中插入错误类型的对象没有危险。然而,需要指向公共基类的指针的容器中,这些指针 有可能无意中指向不同类型的对象。
设计模式的新形式
C++ 中静态多态性的可用性,带来了实现经典设计模式的新方法。以桥接模式为例,使用动态多态实现如下:
cpp
#include <iostream>
#include <memory>
class Implementation {
public:
virtual void do_something() = 0;
};
class ImplA : public Implementation {
public:
void do_something() override {
std::cout << "ImplA::do_something()" << std::endl;
}
};
class ImplB : public Implementation {
public:
void do_something() override {
std::cout << "ImplB::do_something()" << std::endl;
}
};
class Inference {
private:
Implementation* impl;
public:
explicit Inference(Implementation* impl): impl(impl) {}
void do_something() {
impl->do_something();
}
};
int main() {
ImplA a{};
ImplB b{};
Inference inference(&a); // 在接口的不同实现之间切换
inference.do_something();
inference = Inference(&b); // 在接口的不同实现之间切换
inference.do_something();
return 0;
}
而使用静态多态:
cpp
#include <iostream>
#include <memory>
class ImplA {
public:
void do_something() {
std::cout << "ImplA::do_something()" << std::endl;
}
};
class ImplB {
public:
void do_something() {
std::cout << "ImplB::do_something()" << std::endl;
}
};
template<typename T>
class Inference {
private:
T impl;
public:
explicit Inference(T const& impl): impl(std::move(impl)) {}
void do_something() {
impl.do_something();
}
};
int main() {
Inference inference(ImplA{});
inference.do_something();
Inference inferenceb{ImplB{}};
inferenceb.do_something();
return 0;
}
如果在编译时就知道实现的类型,那么可以利用模板的强大功能 ,带来更好的类型 安全性 (部分原因是避免了指针转换) 和性能。
相比于动态多态性,模板利用了静态多态性在编译时解析接口的优势,实现了更高的运行效率,尤其是在处理像容器和迭代器这类场景时,由于迭代器轻量级的特性,采用虚函数机制的成本过高且性能低下。