C++ std::variant的std::visit访问者模式实现
std::variant是C++17引入的类型安全联合类型替代方案,它允许在同一个对象中存储不同类型的数据。访问者模式是一种设计模式,允许在不修改原有类结构的情况下添加新操作。将std::variant与std::visit结合使用,可以实现强大的访问者模式。
1. 准备工作
确保你的编译器支持C++17或更高版本。现代编译器如GCC 7+、Clang 5+和MSVC 19.12+通常都支持这些特性。
包含必要的头文件:
#include <variant>
#include <iostream>
2. 基础std::variant用法
创建一个可以存储不同类型的std::variant对象:
std::variant<int, double, std::string> var;
赋值不同类型的值:
var = 42; // 存储int
var = 3.14; // 存储double
var = "Hello variant"; // 存储string
检查当前存储的类型:
if (std::holds_alternative<int>(var)) {
std::cout << "It's an int: " << std::get<int>(var) << std::endl;
}
3. 访问者模式基础概念
理解访问者模式的核心思想:将操作与数据结构分离。通过访问者类封装对数据的操作,使得可以轻松添加新操作而无需修改数据结构本身。
认识std::visit的作用:std::visit是一个函数模板,它接受一个访问者对象和一个std::variant,然后对variant中当前存储的值调用访问者的适当操作。
4. 创建自定义访问者
定义一个访问者类,实现operator()来处理每种可能的类型:
struct MyVisitor {
void operator()(int i) const {
std::cout << "Int: " << i << std::endl;
}
void operator()(double d) const {
std::cout << "Double: " << d << std::endl;
}
void operator()(const std::string& s) const {
std::cout << "String: " << s << std::endl;
}
};
使用std::visit应用这个访问者:
std::visit(MyVisitor{}, var);
5. 实现返回值的访问者
修改访问者使其可以为每种类型返回特定值:
struct ReturnVisitor {
std::string operator()(int i) const {
return "Int: " + std::to_string(i);
}
std::string operator()(double d) const {
return "Double: " + std::to_string(d);
}
std::string operator()(const std::string& s) const {
return "String: " + s;
}
};
获取访问结果:
std::string result = std::visit(ReturnVisitor{}, var);
std::cout << result << std::endl;
6. 使用lambda表达式作为访问者
定义一个lambda表达式作为访问者:
auto lambdaVisitor = [](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << "Int: " << arg << std::endl;
} else if constexpr (std::is_same_v<T, double>) {
std::cout << "Double: " << arg << std::endl;
} else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "String: " << arg << std::endl;
}
};
std::visit(lambdaVisitor, var);
注意,这里使用了C++17的if constexpr特性,可以根据编译期条件执行不同的代码路径。
7. 多重variant的访问者模式
处理多个std::variant的情况:
using Var1 = std::variant<int, double>;
using Var2 = std::variant<std::string, bool>;
struct MultiVisitor {
void operator()(int i, const std::string& s) const {
std::cout << "Int and String: " << i << ", " << s << std::endl;
}
void operator()(int i, bool b) const {
std::cout << "Int and Bool: " << i << ", " << std::boolalpha << b << std::endl;
}
void operator()(double d, const std::string& s) const {
std::cout << "Double and String: " << d << ", " << s << std::endl;
}
void operator()(double d, bool b) const {
std::cout << "Double and Bool: " << d << ", " << std::boolalpha << b << std::endl;
}
};
Var1 var1 = 42;
Var2 var2 = std::string("Hello");
std::visit(MultiVisitor{}, var1, var2);
8. 实际应用示例:表达式求值器
实现一个简单的表达式求值器,可以处理整数、浮点数、加法、减法、乘法和除法:
#include <variant>
#include <iostream>
#include <string>
#include <memory>
// 定义表达式类型
struct Int;
struct Double;
struct Add;
struct Sub;
struct Mul;
struct Div;
using Expr = std::variant<
Int, Double,
std::unique_ptr<Add>,
std::unique_ptr<Sub>,
std::unique_ptr<Mul>,
std::unique_ptr<Div>
>;
// 表达式节点
struct Int {
int value;
};
struct Double {
double value;
};
struct Add {
Expr left;
Expr right;
};
struct Sub {
Expr left;
Expr right;
};
struct Mul {
Expr left;
Expr right;
};
struct Div {
Expr left;
Expr right;
};
// 求值访问者
struct EvalVisitor {
double operator()(const Int& i) const {
return i.value;
}
double operator()(const Double& d) const {
return d.value;
}
double operator()(const std::unique_ptr<Add>& add) const {
return std::visit(EvalVisitor{}, add->left) + std::visit(EvalVisitor{}, add->right);
}
double operator()(const std::unique_ptr<Sub>& sub) const {
return std::visit(EvalVisitor{}, sub->left) - std::visit(EvalVisitor{}, sub->right);
}
double operator()(const std::unique_ptr<Mul>& mul) const {
return std::visit(EvalVisitor{}, mul->left) * std::visit(EvalVisitor{}, mul->right);
}
double operator()(const std::unique_ptr<Div>& div) const {
double left = std::visit(EvalVisitor{}, div->left);
double right = std::visit(EvalVisitor{}, div->right);
if (right == 0.0) {
throw std::runtime_error("Division by zero");
}
return left / right;
}
};
// 打印访问者
struct PrintVisitor {
std::string operator()(const Int& i) const {
return std::to_string(i.value);
}
std::string operator()(const Double& d) const {
return std::to_string(d.value);
}
std::string operator()(const std::unique_ptr<Add>& add) const {
return "(" + std::visit(PrintVisitor{}, add->left) + " + " +
std::visit(PrintVisitor{}, add->right) + ")";
}
std::string operator()(const std::unique_ptr<Sub>& sub) const {
return "(" + std::visit(PrintVisitor{}, sub->left) + " - " +
std::visit(PrintVisitor{}, sub->right) + ")";
}
std::string operator()(const std::unique_ptr<Mul>& mul) const {
return "(" + std::visit(PrintVisitor{}, mul->left) + " * " +
std::visit(PrintVisitor{}, mul->right) + ")";
}
std::string operator()(const std::unique_ptr<Div>& div) const {
return "(" + std::visit(PrintVisitor{}, div->left) + " / " +
std::visit(PrintVisitor{}, div->right) + ")";
}
};
int main() {
// 创建表达式:(5 + 3) * (10 / 2)
Expr expr = std::make_unique<Mul>(
Add{Int{5}, Int{3}},
Div{Int{10}, Int{2}}
);
// 求值
double result = std::visit(EvalVisitor{}, expr);
std::cout << "Result: " << result << std::endl;
// 打印表达式
std::string exprStr = std::visit(PrintVisitor{}, expr);
std::cout << "Expression: " << exprStr << std::endl;
return 0;
}
9. 高级技巧和注意事项
使用std::monostate作为variant的默认类型,可以表示"无值"状态:
std::variant<std::monostate, int, std::string> var;
var = std::monostate{};
处理variant中未包含值的情况(使用valueless_by_exception状态):
try {
// 可能抛出异常的代码
var = /* 可能抛出异常的操作 */;
} catch (...) {
// variant进入valueless_by_exception状态
if (var.valueless_by_exception()) {
std::cout << "Variant is in invalid state" << std::endl;
}
}
使用std::variant_size和std::variant_alternative进行编译期检查:
if constexpr (std::variant_size_v<decltype(var) > 0) {
// variant至少有一种类型
}
避免在访问者中使用异常,这可能导致variant进入无效状态。
10. 性能考虑
注意std::visit的性能通常比传统的虚函数调用稍差,但类型安全性更高。
考虑在性能关键路径上对热点代码进行基准测试,评估使用std::visit的代价。
权衡类型安全与性能的取舍,根据应用场景做出合适选择。

暂无评论,快来抢沙发吧!