如何用std::variant实现多方法运行时多态(优化访客类冗余)
使用std::variant实现类成员函数的便捷调用
现有场景是用std::variant模拟运行时多态,多个类拥有50个同名方法,但当前为每个方法写单独访客类的方式扩展性极差。希望实现类似variant_variable.method_name(arguments_list)的调用方式,替代逐个写CallMethodN访客的繁琐操作。
原实现代码:
#include <iostream> #include <variant> class Derived1 { public: void method1() const { std::cout << "calling derived1 method1!" << std::endl; } void method2() const { std::cout << "calling derived1 method2!" << std::endl; } void method3() const { std::cout << "calling derived1 method3!" << std::endl; } }; class Derived2 { public: void method1() const { std::cout << "calling derived2 method1!" << std::endl; } void method2() const { std::cout << "calling derived2 method2!" << std::endl; } void method3() const { std::cout << "calling derived2 method3!" << std::endl; } }; struct CallMethod1 { void operator()(const Derived1& d1) { d1.method1(); } void operator()(const Derived2& d2) { d2.method1(); } }; struct CallMethod2 { void operator()(const Derived1& d1) { d1.method2(); } void operator()(const Derived2& d2) { d2.method2(); } }; struct CallMethod3 { void operator()(const Derived1& d1) { d1.method3(); } void operator()(const Derived2& d2) { d2.method3(); } }; int main() { std::variant<Derived1, Derived2> var{}; std::visit(CallMethod1{}, var); std::visit(CallMethod2{}, var); std::visit(CallMethod3{}, var); }
可行方案
方案一:C++20模板Lambda直接调用
如果项目支持C++20,这是最简洁的方案,不需要额外定义任何访客类,直接在std::visit里用模板Lambda:
#include <iostream> #include <variant> // Derived1、Derived2类保持不变 int main() { std::variant<Derived1, Derived2> var{}; // 直接调用方法,语法接近目标需求 std::visit([](const auto& obj) { obj.method1(); }, var); std::visit([](const auto& obj) { obj.method2(); }, var); std::visit([](const auto& obj) { obj.method3(); }, var); // 带参数的方法也适用,比如如果method有参数: // std::visit([](const auto& obj) { obj.method4(123, "test"); }, var); }
如果觉得重复写std::visit麻烦,可以封装一个辅助函数:
template <typename Func, typename Var> auto call_on_var(Var&& var, Func&& func) { return std::visit(std::forward<Func>(func), std::forward<Var>(var)); } // 调用时更简洁: call_on_var(var, [](const auto& obj) { obj.method1(); });
方案二:通用模板访客类
如果用的是C++17及以下,可以写一个通用的模板访客,通过成员函数指针指定要调用的方法,避免重复写CallMethodN:
#include <iostream> #include <variant> // Derived1、Derived2类保持不变 // 通用成员函数调用访客 template <auto MemFunc> struct CallMember { template <typename T, typename... Args> auto operator()(const T& obj, Args&&... args) const -> decltype((obj.*MemFunc)(std::forward<Args>(args)...)) { return (obj.*MemFunc)(std::forward<Args>(args)...); } }; // 辅助函数简化调用 template <auto MemFunc, typename Var, typename... Args> auto call_variant(Var&& var, Args&&... args) { return std::visit(CallMember<MemFunc>{}, std::forward<Var>(var), std::forward<Args>(args)...); } int main() { std::variant<Derived1, Derived2> var{}; // 调用时指定成员函数指针,签名一致的方法通用 call_variant<&Derived1::method1>(var); call_variant<&Derived2::method2>(var); call_variant<&Derived1::method3>(var); }
方案三:包装Variant为自定义类(完全模拟成员调用语法)
如果想要完全实现var.method_name()的语法,可以把std::variant包装到自定义类中,为每个方法实现转发:
#include <iostream> #include <variant> class Derived1 { public: void method1() const { std::cout << "calling derived1 method1!" << std::endl; } void method2() const { std::cout << "calling derived1 method2!" << std::endl; } void method3() const { std::cout << "calling derived1 method3!" << std::endl; } }; class Derived2 { public: void method1() const { std::cout << "calling derived2 method1!" << std::endl; } void method2() const { std::cout << "calling derived2 method2!" << std::endl; } void method3() const { std::cout << "calling derived2 method3!" << std::endl; } }; class PolyVariant { private: std::variant<Derived1, Derived2> var_; public: // 构造函数支持直接传入Derived1或Derived2对象 PolyVariant() = default; PolyVariant(const Derived1& d1) : var_(d1) {} PolyVariant(const Derived2& d2) : var_(d2) {} // 为每个方法实现转发,内部用std::visit调用对应类的方法 void method1() const { std::visit([](const auto& obj) { obj.method1(); }, var_); } void method2() const { std::visit([](const auto& obj) { obj.method2(); }, var_); } void method3() const { std::visit([](const auto& obj) { obj.method3(); }, var_); } // 如果方法需要修改对象,提供非const版本 void method1() { std::visit([](auto& obj) { obj.method1(); }, var_); } }; int main() { PolyVariant var{}; var.method1(); // 完全符合目标调用语法 var.method2(); var.method3(); // 切换到Derived2实例 PolyVariant var2{Derived2{}}; var2.method1(); }
这个方案虽然需要为每个方法写转发函数,但相比原方案,代码更集中,也完全实现了你想要的调用方式。
内容的提问来源于stack exchange,提问作者Harry
相关产品推荐
相关产品推荐

