当前位置: 首页 > news >正文

【C++】类型擦除 + 工厂模式,告别 if-else

文章目录

  • 一、使用std::variant和std::visit
  • 二、使用虚函数
  • 参考

需求点:假设一个第三方库有提供了很多Msg类,这些Msg类都提供了固定的一个成员函数,但是却没有虚基函数。如何在自己的项目代码中更好的使用这些Msg类内?

一、使用std::variant和std::visit

#include <iostream>
#include <memory>  // for std::unique_ptr and std::make_unique
#include <string>
#include <utility>  // for std::move
#include <variant>
#include <vector>// 外部库 Start
struct MoveMsg {int x;int y;void speak() { std::cout << "Move " << x << ", " << y << '\n'; }
};struct JumpMsg {int height;void speak() { std::cout << "Jump " << height << '\n'; }
};struct SleepMsg {int time;void speak() { std::cout << "Sleep " << time << '\n'; }
};struct ExitMsg {void speak() { std::cout << "Exit" << '\n'; }
};
// 外部库 Endint main() {using Msg = std::variant<MoveMsg, JumpMsg, SleepMsg, ExitMsg>;std::vector<Msg> msgs;msgs.push_back(MoveMsg{1, 2});msgs.push_back(JumpMsg{1});for (auto&& msg : msgs) {std::visit([](auto& msg) { msg.speak(); }, msg);}return 0;
}

二、使用虚函数

C++20语法

#include <iostream>
#include <memory>  // for std::unique_ptr and std::make_unique
#include <string>
#include <utility>  // for std::move
#include <variant>
#include <vector>// 外部库 Start
struct MoveMsg {int x;int y;void speak() { std::cout << "Move " << x << ", " << y << '\n'; }
};struct JumpMsg {int height;void speak() { std::cout << "Jump " << height << '\n'; }void happy() { std::cout << "happy " << height << '\n'; }
};struct SleepMsg {int time;void speak() { std::cout << "Sleep " << time << '\n'; }
};struct ExitMsg {void speak() { std::cout << "Exit" << '\n'; }
};
// 外部库 Endstruct MsgBase {virtual void speak() = 0;virtual void happy() = 0;virtual std::shared_ptr<MsgBase> clone() const = 0;virtual ~MsgBase() = default;
};//内部代码使用MsgBase去包装库的speak()和happy()函数
template <class Msg>
struct MsgImpl : MsgBase {Msg msg;template <class ...Ts>MsgImpl(Ts &&...ts) : msg{std::forward<Ts>(ts)...} {}void speak() override {msg.speak();}void happy() override {if constexpr (requires {msg.happy();} ){msg.happy();}else{std::cout<< "no happy\n";}}std::shared_ptr<MsgBase> clone() const override {return std::make_shared<MsgImpl<Msg>>(msg);}
};template <class Msg, class ...Ts>
std::shared_ptr<MsgBase> makeMsg(Ts &&...ts) {return std::make_shared<MsgImpl<Msg>>(std::forward<Ts>(ts)...);
}int main() {std::vector<std::shared_ptr<MsgBase>> msgs;msgs.push_back(makeMsg<MoveMsg>(1, 2));msgs.push_back(makeMsg<JumpMsg>(1));for (auto&& msg : msgs) {msg->speak(); msg->happy(); }return 0;
}

C++14写法1

#include <iostream>
#include <memory>  // for std::unique_ptr and std::make_unique
#include <string>
#include <utility>  // for std::move
#include <variant>
#include <vector>// 外部库 Start
struct MoveMsg {int x;int y;void speak() { std::cout << "Move " << x << ", " << y << '\n'; }
};struct JumpMsg {int height;void speak() { std::cout << "Jump " << height << '\n'; }void happy() { std::cout << "happy " << height << '\n'; }
};struct SleepMsg {int time;void speak() { std::cout << "Sleep " << time << '\n'; }
};struct ExitMsg {void speak() { std::cout << "Exit" << '\n'; }
};
// 外部库 Endstruct MsgBase {virtual void speak() = 0;virtual void happy() = 0;virtual std::shared_ptr<MsgBase> clone() const = 0;virtual ~MsgBase() = default;
};// 特质,用于检测 happy 方法是否存在
template<typename T>
struct has_happy {
private:typedef char YesType[1];typedef char NoType[2];template <typename C> static YesType& test(decltype(&C::happy));template <typename C> static NoType& test(...);public:enum { value = sizeof(test<T>(0)) == sizeof(YesType) };
};// 函数重载用于调用 happy 或者输出 "no happy"
template <typename T>
typename std::enable_if<has_happy<T>::value>::type call_happy(T& t) {t.happy();
}template <typename T>
typename std::enable_if<!has_happy<T>::value>::type call_happy(T&) {std::cout << "no happy\n";
}// MsgImpl 用于包装库的消息类型
template <class Msg>
struct MsgImpl : MsgBase {Msg msg;template <class ...Ts>MsgImpl(Ts &&...ts) : msg{std::forward<Ts>(ts)...} {}void speak() override {msg.speak();}void happy() override {call_happy(msg);}std::shared_ptr<MsgBase> clone() const override {return std::make_shared<MsgImpl<Msg>>(msg);}
};template <class Msg, class ...Ts>
std::shared_ptr<MsgBase> makeMsg(Ts &&...ts) {return std::make_shared<MsgImpl<Msg>>(std::forward<Ts>(ts)...);
}int main() {std::vector<std::shared_ptr<MsgBase>> msgs;msgs.push_back(makeMsg<MoveMsg>(1, 2));msgs.push_back(makeMsg<JumpMsg>(1));for (auto&& msg : msgs) {msg->speak(); msg->happy(); }return 0;
}

has_happy 模板:

  • has_happy 模板用于检测 Msg 类中是否有 happy 方法。
  • SFINAE 技术用于检查特定方法是否存在。
  • test 函数用于确定 happy 方法的存在性,如果存在,返回 sizeof(char),否则返回 sizeof(int)。

call_happy 函数:

  • 根据 std::integral_constant 的值,选择调用 happy 方法或输出 “no happy”。
  • std::true_type 和 std::false_type 是 std::integral_constant 的特例化,分别表示 true 和 false。

C++14写法2:

#include <iostream>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include <type_traits>// 外部库 Start
struct MoveMsg {int x;int y;void speak() { std::cout << "Move " << x << ", " << y << '\n'; }
};struct JumpMsg {int height;void speak() { std::cout << "Jump " << height << '\n'; }void happy() { std::cout << "happy " << height << '\n'; }
};struct SleepMsg {int time;void speak() { std::cout << "Sleep " << time << '\n'; }
};struct ExitMsg {void speak() { std::cout << "Exit" << '\n'; }
};
// 外部库 Endstruct MsgBase {virtual void speak() = 0;virtual void happy() = 0;virtual std::shared_ptr<MsgBase> clone() const = 0;virtual ~MsgBase() = default;
};// 检测 happy 方法存在性的模板
template<typename T>
struct has_happy {// 利用 SFINAE 技术检测 happy 方法template<typename U>static auto test(int) -> decltype(std::declval<U>().happy(), std::true_type{});template<typename>static std::false_type test(...);static constexpr bool value = decltype(test<T>(0))::value;
};// MsgImpl 包装库的消息类型
template <class Msg>
struct MsgImpl : MsgBase {Msg msg;template <class ...Ts>MsgImpl(Ts&&... ts) : msg{std::forward<Ts>(ts)...} {}void speak() override {msg.speak();}void happy() override {call_happy(msg, std::integral_constant<bool, has_happy<Msg>::value>{});}std::shared_ptr<MsgBase> clone() const override {return std::make_shared<MsgImpl<Msg>>(msg);}private:// 当 Msg 有 happy 方法时调用template<typename T>void call_happy(T& t, std::true_type) {t.happy();}// 当 Msg 没有 happy 方法时调用template<typename T>void call_happy(T&, std::false_type) {std::cout << "no happy\n";}
};template <class Msg, class ...Ts>
std::shared_ptr<MsgBase> makeMsg(Ts&&... ts) {return std::make_shared<MsgImpl<Msg>>(std::forward<Ts>(ts)...);
}int main() {std::vector<std::shared_ptr<MsgBase>> msgs;msgs.push_back(makeMsg<MoveMsg>(1, 2));msgs.push_back(makeMsg<JumpMsg>(1));for (auto&& msg : msgs) {msg->speak(); msg->happy(); }return 0;
}

使用特质 has_happy 来检测 happy 方法是否存在。
使用 enable_if 和函数重载来根据特质的值调用不同的实现。

06:36

参考

  • 【C++】类型擦除 + 工厂模式,告别 if-else
  • 代码

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • My sql 安装,环境搭建
  • 【C++PCL】点云处理误差RMSE值计算
  • Spring Boot与RSocket的集成
  • IDEA常用技巧荟萃:精通开发利器的艺术
  • 字符串中的注意事项
  • Postman深度解析:打造高效接口测试自动化流程
  • firewalld(6)自定义services、ipset
  • Spring 使用 Ehcache 技术指南
  • OFDM中采样频率与带宽的关系
  • 什么是设计模式以及常见的例子(如单例、工厂、观察者等)
  • IT高手修炼手册(3)程序员命令
  • 如何在Java中实现实时数据同步与更新
  • 基于springboot+vue+uniapp的高校宿舍信息管理系统小程序
  • Kotlin算法:把一个整数向上取值为最接近的2的幂指数值
  • 【面向就业的Linux基础】从入门到熟练,探索Linux的秘密(十一)-git(3)
  • $translatePartialLoader加载失败及解决方式
  • [译]如何构建服务器端web组件,为何要构建?
  • 【挥舞JS】JS实现继承,封装一个extends方法
  • canvas实际项目操作,包含:线条,圆形,扇形,图片绘制,图片圆角遮罩,矩形,弧形文字...
  • Java方法详解
  • java正则表式的使用
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • Rancher如何对接Ceph-RBD块存储
  • REST架构的思考
  • spring cloud gateway 源码解析(4)跨域问题处理
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • TCP拥塞控制
  • 如何设计一个微型分布式架构?
  • 使用前端开发工具包WijmoJS - 创建自定义DropDownTree控件(包含源代码)
  • 通过git安装npm私有模块
  • 原生js练习题---第五课
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • Java性能优化之JVM GC(垃圾回收机制)
  • ​​​​​​​STM32通过SPI硬件读写W25Q64
  • ​软考-高级-信息系统项目管理师教程 第四版【第23章-组织通用管理-思维导图】​
  • #13 yum、编译安装与sed命令的使用
  • #pragma pack(1)
  • (Java入门)抽象类,接口,内部类
  • (SERIES12)DM性能优化
  • (附源码)springboot炼糖厂地磅全自动控制系统 毕业设计 341357
  • (六)库存超卖案例实战——使用mysql分布式锁解决“超卖”问题
  • (三)mysql_MYSQL(三)
  • (十六)一篇文章学会Java的常用API
  • (十一)手动添加用户和文件的特殊权限
  • (四)js前端开发中设计模式之工厂方法模式
  • (转)自己动手搭建Nginx+memcache+xdebug+php运行环境绿色版 For windows版
  • ***监测系统的构建(chkrootkit )
  • .gitignore文件忽略的内容不生效问题解决
  • .NET CF命令行调试器MDbg入门(一)
  • .NET Core实战项目之CMS 第十二章 开发篇-Dapper封装CURD及仓储代码生成器实现
  • .net oracle 连接超时_Mysql连接数据库异常汇总【必收藏】
  • .NET 设计一套高性能的弱事件机制
  • .net 受管制代码
  • .Net(C#)自定义WinForm控件之小结篇
  • .net6解除文件上传限制。Multipart body length limit 16384 exceeded