在 C++17 之前,C++ 的多态通常通过继承和虚函数实现,但这会带来运行时类型检查、指针管理以及可能的内存占用。C++17 引入了 std::variant,它允许我们在同一个变量中安全地存放多种不同类型的值,且在编译期间能够保证类型安全。下面我们通过一个实例,演示如何使用 std::variant 实现一种轻量级的多态,并避免传统继承体系中的一些缺点。
1. 场景描述
假设我们需要处理不同形状(圆形、矩形、三角形)的面积计算。传统方式会定义一个基类 Shape,并让每个子类实现 area()。但如果我们想在不引入多态的情况下实现同样功能,可以考虑使用 std::variant。
2. 数据结构定义
#include <variant>
#include <cmath>
#include <iostream>
#include <vector>
// 每种形状对应一个结构体
struct Circle {
double radius;
};
struct Rectangle {
double width;
double height;
};
struct Triangle {
double a, b, c; // 三条边
};
// 将所有可能的形状类型聚合在一个 variant
using ShapeVariant = std::variant<Circle, Rectangle, Triangle>;
3. 面积计算
我们可以为每个结构体单独实现面积计算,然后用 std::visit 对 variant 进行访问。
double area(const Circle& c) {
return M_PI * c.radius * c.radius;
}
double area(const Rectangle& r) {
return r.width * r.height;
}
double area(const Triangle& t) {
// 海伦公式
double s = (t.a + t.b + t.c) / 2.0;
return std::sqrt(s * (s - t.a) * (s - t.b) * (s - t.c));
}
// 统一接口
double area(const ShapeVariant& shape) {
return std::visit([](auto&& arg) { return area(arg); }, shape);
}
4. 使用示例
int main() {
std::vector <ShapeVariant> shapes{
Circle{5.0},
Rectangle{4.0, 6.0},
Triangle{3.0, 4.0, 5.0}
};
for (const auto& shape : shapes) {
std::cout << "Area: " << area(shape) << '\n';
}
return 0;
}
运行结果:
Area: 78.5398
Area: 24
Area: 6
5. 优势与注意事项
- 类型安全:
std::variant在编译期知道所有可能的类型,访问错误会被编译器捕获。 - 无继承开销:不需要虚表和动态分配,减少了内存占用和指针间接访问成本。
- 灵活性:可以轻松添加新的形状,只需在
variant中加入新类型,并实现对应的面积函数。 - 不可变性:
std::variant的值可以是不可变的,保证线程安全。
然而,也有需要注意的地方:
- 当形状数量很大且形状复杂时,
std::visit的调用会有一定开销,尤其是涉及到类型检查的分支。 - 对于需要在运行时动态添加/删除形状类型的场景,继承体系可能更为灵活。
6. 进一步扩展
如果想要对形状做更多操作(如绘制、平移、缩放等),可以为每个结构体添加相应的方法,或使用 std::visit 结合一个统一的“操作”结构体来实现。
struct DrawVisitor {
void operator()(const Circle& c) const { /* 绘制圆 */ }
void operator()(const Rectangle& r) const { /* 绘制矩形 */ }
void operator()(const Triangle& t) const { /* 绘制三角形 */ }
};
void draw(const ShapeVariant& shape) {
std::visit(DrawVisitor{}, shape);
}
7. 小结
std::variant 为 C++ 提供了一种类型安全且高效的多态替代方案。通过 std::visit,我们可以在编译期明确每种类型的行为,避免传统虚函数导致的运行时开销。尤其在形状、命令模式、事件处理等需要多类型统一管理的场景中,std::variant 能让代码更简洁、可维护。