在 C++17 标准中,std::variant 为我们提供了一种强类型的“和”类型(sum type),它可以存储多种类型中的任意一种,同时保证类型安全。相比传统的虚表多态,std::variant 更加轻量、无运行时开销,并且在异常安全方面表现优异。本文将通过一个完整的示例,展示如何使用 std::variant 来实现多态,并讨论其异常安全特点。
1. 需求场景
假设我们有一个形状层次结构:圆形、矩形、三角形。我们需要在同一容器中存放这些对象,并对它们执行统一的面积计算。传统实现:
class Shape { public: virtual double area() const = 0; };
class Circle : public Shape { ... };
class Rectangle : public Shape { ... };
此方式需要使用指针或引用,并且可能出现空指针、虚表开销、对象多态性带来的异常传播问题。
2. 采用 std::variant 的实现
#include <variant>
#include <iostream>
#include <cmath>
#include <vector>
struct Circle {
double radius;
double area() const { return M_PI * radius * radius; }
};
struct Rectangle {
double width, height;
double area() const { return width * height; }
};
struct Triangle {
double a, b, c; // 三边长
double area() const {
double s = (a + b + c) / 2.0;
return std::sqrt(s * (s - a) * (s - b) * (s - c));
}
};
using Shape = std::variant<Circle, Rectangle, Triangle>;
我们用 Shape 这个 variant 包装所有可能的形状。
2.1 访问与多态
std::variant 提供了两种访问方式:std::get(按索引或类型访问)和 std::visit(访客模式)。为了保持类型安全,推荐使用 std::visit:
double compute_area(const Shape& shape) {
return std::visit([](auto&& s) -> double { return s.area(); }, shape);
}
此 lambda 是一个通用的“访客”,其参数 s 由 variant 自动推断为实际存储的类型。无需写一堆 if-else 或 dynamic_cast。
2.2 例子:容器与总面积
int main() {
std::vector <Shape> shapes = {
Circle{3.0},
Rectangle{4.0, 5.0},
Triangle{3.0, 4.0, 5.0}
};
double total_area = 0.0;
for (const auto& shape : shapes) {
total_area += compute_area(shape);
}
std::cout << "Total area: " << total_area << '\n';
}
编译并运行即可得到总面积。整个过程不涉及任何虚函数调用,且所有类型都在栈上分配,效率更高。
3. 异常安全
3.1 variant 的内部实现
std::variant 内部通常采用联合(union)加上 std::aligned_storage 存储值,并维护一个“active index”。在切换存储值时,它会先调用当前类型的析构,然后构造新类型。若构造过程中抛出异常,variant 需要确保已处于一个可恢复的状态。
标准保证:
- 在构造期间抛出异常时,
variant仍保持不变(原始值仍然有效)。 - 在
variant赋值或移动时,如果新值的构造抛出异常,旧值保持不变。
这意味着只要你使用 std::variant 的成员函数(如 operator=、emplace 等),都可以获得强异常安全保证。
3.2 访问异常
std::visit 也提供异常安全。访客函数如果抛出异常,visit 本身会直接将异常向上传递,不会破坏 variant 的状态。
3.3 与传统多态的对比
传统多态使用虚表时,若在构造函数或成员函数中抛异常,可能导致对象处于半构造状态,随后析构时出现未定义行为。variant 的实现更像“值类型”,异常传播更直观。
4. 何时使用 std::variant
- 有限且已知的类型集合:如形状、消息类型、错误码等。
- 无多态开销需求:需要更高性能、无虚表的实现。
- 希望获得异常安全保证:
variant的内部实现天然满足强异常安全。
5. 结语
std::variant 是 C++17 引入的强大工具,它让我们在保持类型安全的同时,轻松实现多态逻辑,并且在异常安全方面表现卓越。只要满足“类型集合固定、无需继承”这一前提,variant 都能成为你代码中的首选。希望本文能帮助你在项目中更好地利用 std::variant,写出既高效又安全的 C++ 代码。