【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'; }
};
// 外部库 End

int 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'; }
};
// 外部库 End

struct 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'; }
};
// 外部库 End

struct 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'; }
};
// 外部库 End

struct 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
  • 代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/779230.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

前后端分离系统

前后端分离是一种现代软件架构模式&#xff0c;特别适用于Web应用开发&#xff0c;它强调将用户界面&#xff08;前端&#xff09;与服务器端应用逻辑&#xff08;后端&#xff09;相分离。两者通过API接口进行数据交互。这种架构模式的主要优势在于提高开发效率、维护性和可扩…

【LInux】从动态库的加载深入理解页表机制

&#x1f490; &#x1f338; &#x1f337; &#x1f340; &#x1f339; &#x1f33b; &#x1f33a; &#x1f341; &#x1f343; &#x1f342; &#x1f33f; &#x1f344;&#x1f35d; &#x1f35b; &#x1f364; &#x1f4c3;个人主页 &#xff1a;阿然成长日记 …

Xilinx FPGA:vivado关于同步fifo的两个小实验

一、实验一&#xff1a;在同步fifo里写一个读一个&#xff08;写入是8个位宽&#xff0c;读出是16个位宽&#xff09; 程序&#xff1a; timescale 1ns / 1ps //要求写一个读一个 //读写时钟一致&#xff0c;写是8个位宽&#xff0c;读是16个位宽 module sync_fifo_test(inpu…

Nuxt框架中内置组件详解及使用指南(一)

title: Nuxt框架中内置组件详解及使用指南&#xff08;一&#xff09; date: 2024/7/6 updated: 2024/7/6 author: cmdragon excerpt: 本文详细介绍了Nuxt框架中的两个内置组件和的使用方法与功能。确保包裹的内容仅在客户端渲染&#xff0c;适用于处理浏览器特定功能或异步…

ubuntu 22 安装 lua 环境 编译lua cjson 模块

在 windows 下使用 cygwin 编译 lua 和 cjson 简直就是灾难&#xff0c;最后还是到 ubuntu 下完成了。 1、下载lua源码&#xff08;我下载的 5.1 版本&#xff0c;后面还有一个小插曲), 直接解压编译&#xff0c;遇到一个 readline.h not found 的问题&#xff0c;需要安装 re…

MySQL篇三:数据类型

文章目录 前言1. 数值类型1.1 tinyint类型1.2 bit类型1.3 小数类型1.3.1 float1.3.2 decimal 2. 字符串类型2.1 char2.2 varchar2.3 char和varchar比较 3. 日期类型4. enum和set 前言 数据类型分类&#xff1a; 1. 数值类型 1.1 tinyint类型 在MySQL中&#xff0c;整型可以指…

论文略读:Learning and Forgetting Unsafe Examples in Large Language Models

随着发布给公众的大语言模型&#xff08;LLMs&#xff09;数量的增加&#xff0c;迫切需要了解这些模型从第三方定制的微调数据中学习的安全性影响。论文研究了在包含不安全内容的噪声定制数据上微调的LLMs的行为&#xff0c;这些数据集包含偏见、毒性和有害性 发现虽然对齐的L…

【Unity数据存储】Unity中使用SqLite数据库进行数据持久化

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 专栏交流&#x1f9e7;&…

Ubuntu 22.04远程自动登录桌面环境

如果需要远程自动登录桌面环境&#xff0c;首先需要将Ubuntu的自动登录打开&#xff0c;在【settings】-【user】下面 然后要设置【Sharing】进行桌面共享&#xff0c;Ubuntu有自带的桌面共享功能&#xff0c;不需要另外去安装xrdp或者vnc之类的工具了 点开【Remote Desktop】…

解决IDEA每次新建项目都需要重新配置maven的问题

每次打开IDEA都要重新配置maven&#xff0c;这是因为在DEA中分为项目设置和全局设置&#xff0c;这个时候我们就需要去到全局中设置maven了。我用的是IntelliJ IDEA 2023.3.4 (Ultimate Edition)&#xff0c;以此为例。 第一步&#xff1a;打开一个空的IDEA&#xff0c;选择左…

大学生电子设计大赛超全资料分享

超全大学生电子设计大赛项目合集免费分享 电赛竞赛资料大全&#xff0c;新增竞赛空间电子设计资料。包含嵌入式硬件和软件开发的学习资料&#xff0c;包括PCB教程&#xff0c;单片机例程&#xff0c; 单片机课程设计毕业设计参考资料、项目设计方案&#xff0c;源码和开发文档…

孟德尔随机化 --痛风与酒精消耗量

写在前面 最近看了微信公众号&#xff0c;jimmy谈到生信与基础之间&#xff0c;个人觉得生信与基础技术是无高低之分的&#xff0c;本质上都是科研中为了证实结果的一个工具。生信的实质是用计算机分析数据&#xff0c;接触基础比较好&#xff0c;感觉是实验操作。分析上游为实…

初学嵌入式是弄linux还是单片机?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「单片机的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“666”之后私信回复“666”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;1、先入门了51先学了89c52…

Windows安装超好用的截图工具——Snipaste

1、下载 官网&#xff1a;https://zh.snipaste.com/ 2、安装 &#xff08;1&#xff09;解压下载的压缩包 &#xff08;2&#xff09;选中Snipaste.exe文件&#xff0c;右键发送到 -- > 桌面快捷方式 &#xff08;3&#xff09;双击桌面Snipaste图标&#xff0c;桌面右下…

3.js - 裁剪平面(clipIntersection:交集、并集)

看图 代码 // ts-nocheck// 引入three.js import * as THREE from three// 导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls// 导入lil.gui import { GUI } from three/examples/jsm/libs/lil-gui.module.min.js// 导入tween import …

docker部署onlyoffice,开启JWT权限校验Token

原来的部署方式 之前的方式是禁用了JWT&#xff1a; docker run -itd -p 8080:80 --name docserver --network host -e JWT_ENABLEDfalse --restartalways onlyoffice/documentserver:8 新的部署方式 参考文档&#xff1a;https://helpcenter.onlyoffice.com/installation/…

实验1 主成分分析

目 录 二、实验环境... 1 三、实验内容... 1 3.1 导入数据... 2 3.2 求相关系数矩阵.. 3 3.3 数据规范化处理.. 3 3.4 主成分分析... 4 四 实验心得... 5 一、实验目的 &#xff08;1&#xff09;理解主成分分析的思想&#xff1b; &#xff08;2&#xff09;掌握主成分分析方…

Python 异步编程介绍与代码示例

Python 异步编程介绍与代码示例 一、异步编程概述 异步编程是一种编程范式&#xff0c;它旨在处理那些需要等待I/O操作完成或执行耗时任务的情况。在传统的同步编程中&#xff0c;代码会按照顺序逐行执行&#xff0c;直到遇到一个耗时操作&#xff0c;它会阻塞程序的执行直到…

测试人应该懂的!自动化测试必会之数据驱动测试

数据驱动测试 在实际的测试过程中&#xff0c;我们会发现好几组用例都是相同的操作步骤&#xff0c;只是测试数据的不同&#xff0c;而我们往往需要编写多次用例来进行测试&#xff0c;此时我们可以利用数据驱动测试来简化该种操作。 参数化&#xff1a; 输入数据的不同从而产…

【云计算】公有云、私有云、混合云、社区云、多云

公有云、私有云、混合云、社区云、多云 1.云计算的形态1.1 公有云1.2 私有云1.3 混合云1.4 社区云1.5 多云1.5.1 多云和混合云之间的关系1.5.2 多云的用途1.5.3 影子 IT 和多云1.5.4 优缺点 2.不同云形态的对比 1.云计算的形态 张三⾃⼰在家做饭吃&#xff0c;这是 私有云&…