当前位置: 首页 > 科技观察

高端运维:不用继承我也能实现多态

时间:2023-03-23 10:40:29 科技观察

转载本文请联系程序喵公众号。大家应该都知道C++17引入了variant。在这篇文章中,让我们研究一下它有什么用。这个问题的变体是什么?为什么引入变体?如何判断当前存储在variant中的数据类型?为什么变体要与单态匹配?如何使用variant实现多态?Variant类似于union,可以存储多种类型的数据。但任何时候最多只能存储一种类型。说到这里大家可能会有一些疑惑,既然有了union,为什么还要引入variant呢?一定是因为工会有缺点。看看这个union的基本用法:unionMyUnion{inta;浮动b;双c;};voidtest_simple_union(){MyUnionu;u.a=1;std::cout<u;///<单态u=1即将被覆盖;std::cout<(u).a<<"\n";u=std::string("dsd");std::cout<(u).c<<"\n";}使用variant根本不需要手动调用构造函数和析构函数,它会自动处理所有的逻辑,这很方便。这里还有一个问题,就是如何判断variant中当前存放的是什么类型的数据?别着急,后面会介绍。在此之前,我们需要介绍一个知识点:monostate。首先,普通变体使用如下:voidtest_variant(){std::variantvar;变量=12;std::cout<(var)<<"\n";var=12.1f;std::cout<(var)<<"\n";}这也是variant的正常用法。如果我存储自定义类型怎么办?structS{S(inti):value{i}{}intvalue;};voidtest_monostate2(){///<编译失败,如果S没有构造函数,需要加上monostatestd::variant变种;变量=12;std::cout<(var).value<<"\n";}这里会编译失败,因为S没有参数默认构造函数默认不能直接声明,所以需要一个monostate这里要加上,说明它的存储类型默认是monostate。然后它可以这样使用:structS{S(inti):value{i}{}intvalue;};voidtest_monostate(){std::variantvar;变量=12;std::cout<(var).value<<"\n";}那么如何获取variant的内部存储类型呢?事实上,变体有一个index()方法来完成它。看这段代码:voidtest_index(){std::variantvar;///<默认索引为0var=1;std::cout<structget_index;templatestructget_index_impl{};templatestructget_index_impl:std::integral_constant{};templatestructget_index_impl:get_index_impl{};template<类型名T,类型名...Ts>structget_index>:get_index_impl<0,T,Ts...>{};template<类型名T,类型名...Ts>constexprautoget_index_v=get_index::value;usingvariant_t=std::variant;constexprstaticautokPlaceholderIndex=get_index_v;constexprstaticautokIntIndex=get_index_v;constexpr静态自动kFloatIndex=get_index_v;constexprstaticautokStringIndex=get_index_v;通过get_index_v,可以知道该数据类型在variant中的索引,即使以后有变化也不用担心,会自动处理再贴一段它的测试代码:voidtest_using_index(){std::cout<<"kPlaceholderIndex"<(value)<<"\n";休息;casekFloatIndex:std::cout<<"浮点值"<(value)<<"\n";休息;casekStringIndex:std::cout<<"字符串值"<(值)<<"\n";休息;}};variant_t变量;custom_visitor(var);变量=1;custom_visitor(var);var=2.90f;自定义访客(变量);var=std::string("你好世界");custom_visitor(var);var=std::string("你好类型");}intmain(){test_using_index();}结果在这里:kPlaceholderIndex0kIntIndex1kFloatIndex2kStringIndex3placehodlervalueintvalue1floatvalue2.9stringvaluehelloworld方便吗?其实上面的代码,我个人认为也是一种多态,虽然是一个普通的switch-case,但是,我们可以使用std::visit稍微修改一下如何使用std::visit?看这段代码:structVisitor{voidoperator()(inti)const{std::cout<<"int"<var;变量=1;std::visit(访客(),var);var=2.90f;std::visit(访客(),var);var=std::string("你好世界");std::visit(Visitor(),var);}//outputint1float2.9stringhelloworldvisit内部会自动判断当前variant的内部存储类型,进而触发不同的行为。以上是使用函子的访问。事实上,使用lambda表达式更方便:voidtest_visitor_lambda(){std::variantvar;变量=1;std::visit([](constauto&value){std::cout<<"value"<;ifconstexpr(std::is_same_v){std::cout<<"intvalue"<){std::cout<<"floatvalue"<){std::cout<<"字符串值"<<值<<"\n";}},var);}//输出值1value2.9valuehelloworldstringvaluehellotype你也应该意识到你可以使用std::visitwithvariant来实现多态以下是我写的几个变体的多态示例:structA{voidfunc()const{std::cout<<"funcA\n";}};structB{voidfunc()const{std::cout<<"funcB\n";}};structCallFunc{voidoperator()(constA&a){a.func();}voidoperator()(constB&b){b.func();};voidtest_no_param_polymorphism(){std::variantvar;var=A();std::visit(CallFunc{},var);变量=B();std::visit(CallFunc{},var);}上面是无参数的多态,那如果要给函数加点参数怎么办?可以在仿函数中使用成员变量,即:structC{voidfunc(intvalue)const{std::cout<<"funcC"<var;变量=C();std::visit(CallFuncParam{1},var);var=D();std::visit(CallFuncParam{2},var);}或者lambda表达式的捕获方法,即:voidtest_param_lambda_polymorphism(){std::variantvar;整数值=1;autocaller=[&value](constauto&v){v.func(value);};std::visit(caller,变量);值=2;std::visit(caller,var);}这里已经介绍了variant实现多态的完整方案,认为继承是祸患之友。其实也可以考虑用variant来实现多态行为。同样是实现多态,用继承好还是变体好?可以看到这张图:图片来自这个链接:http://cpptruths.blogspot.com/2018/02/inheritance-vs-stdvariant-based.html。有兴趣的可以直接搬家。另外,大家应该更感兴趣的是变体是如何实现的。关于如何实现variant,我找到了这篇文章,写的非常好。可以阅读:https://www.cnblogs.com/qicosmos/p/3416432.html这里是本文的参考链接:hhttps://www.cppstories.com/2020/04/variant-virtual-polymorphism.html/https://stackoverflow.com/questions/52296889/what-are-the-advantages-of-using-stdvariant-as-opposed-to-traditional-polymorphhttps://www.cppstories.com/2018/06/variant/http://cpptruths.blogspot.com/2018/02/inheritance-vs-stdvariant-based.html收工吧。完整代码见:https://github.com/chengxumiaodaren/cpp-learning/tree/master/src/variant