在 C++17 之后,标准库提供了 std::variant,它是一个可容纳多种类型的“类型安全”联合体。与传统的多态(通过继承和虚函数实现)相比,std::variant 在编译期就能保证类型正确性,并且不需要运行时的虚表开销。下面通过一个完整的例子来演示如何利用 std::variant 创建一个“和”类型,并实现基本运算与访问。
1. 需求场景
假设我们需要一个容器,既可以存放整数,也可以存放浮点数,且希望对其进行加法运算。传统做法会是:
class Number { virtual ~Number() = default; virtual double toDouble() const = 0; };
class IntNumber : public Number { /* ... */ };
class FloatNumber : public Number { /* ... */ };
这样做会产生虚函数表,且在使用时需要动态类型判断。
2. 使用 std::variant 的方案
2.1 定义类型
#include <variant>
#include <iostream>
#include <string>
#include <cmath>
using Number = std::variant<int, double>;
2.2 基本操作
- 赋值:直接使用构造函数或
Number n = 5;/Number n = 3.14; - 访问:通过 `std::get (n)` 或 `std::get_if(&n)`;如果类型不匹配会抛出 `std::bad_variant_access`。
Number n1 = 10; // int
Number n2 = 2.5; // double
// 访问
int i = std::get <int>(n1); // 10
double d = std::get <double>(n2); // 2.5
// 或安全访问
if (auto p = std::get_if <int>(&n1)) std::cout << *p << '\n';
2.3 运算实现
我们可以利用 std::visit 对 variant 进行访问并做运算。std::visit 可以接收一个可调用对象(如 lambda)和一个或多个 variant,它会在运行时根据实际类型执行对应的分支。
Number add(const Number& a, const Number& b)
{
return std::visit([](auto&& lhs, auto&& rhs)
{
using L = std::decay_t<decltype(lhs)>;
using R = std::decay_t<decltype(rhs)>;
// 两个都是 int
if constexpr (std::is_same_v<L,int> && std::is_same_v<R,int>)
return Number(static_cast <int>(lhs + rhs));
// int + double 或 double + int
else if constexpr (std::is_same_v<L,int> && std::is_same_v<R,double>)
return Number(static_cast <double>(lhs) + rhs);
else if constexpr (std::is_same_v<L,double> && std::is_same_v<R,int>)
return Number(lhs + static_cast <double>(rhs));
// 两个都是 double
else if constexpr (std::is_same_v<L,double> && std::is_same_v<R,double>)
return Number(lhs + rhs);
}, a, b);
}
2.4 示例与输出
int main()
{
Number n1 = 42;
Number n2 = 3.14;
Number n3 = 7;
Number r1 = add(n1, n2); // int + double => double
Number r2 = add(n3, n1); // int + int => int
std::visit([](auto&& x){
std::cout << x << " (" << typeid(x).name() << ")\n";
}, r1);
std::visit([](auto&& x){
std::cout << x << " (" << typeid(x).name() << ")\n";
}, r2);
return 0;
}
输出示例(取决于编译器):
45.140000 (d)
49 (i)
3. 优点总结
| 方面 | std::variant | 传统虚函数 |
|---|---|---|
| 类型安全 | 编译期检查 | 运行期判断 |
| 性能 | 无虚表 | 有虚表 |
| 可读性 | 简洁 | 继承层次 |
| 可维护性 | 较低 | 可能较高(多继承) |
4. 小贴士
- 访问时使用
std::get_if:如果不确定类型,使用指针返回,避免异常。 - 自定义运算:可以为 variant 定义
operator+等,以使代码更自然。 - 与
std::optional组合:有时需要“可能为空”的多类型值,可以用std::variant<Empty, int, double>或std::optional<std::variant<int,double>>。
5. 结语
std::variant 是 C++17 之后提供的强大工具,它在不牺牲性能的前提下,让我们以更安全、更简洁的方式实现多态。掌握 std::visit 的技巧后,你就能在许多需要“可变类型”场景中替代传统的继承多态方案,写出更现代、更可靠的 C++ 代码。