文章目录

C++ std::variant的std::visit访问者模式实现

发布于 2026-04-23 15:17:30 · 浏览 6 次 · 评论 0 条

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的代价。

权衡类型安全与性能的取舍,根据应用场景做出合适选择。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文