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

发布于:2024-07-07 ⋅ 阅读:(111) ⋅ 点赞:(0)

需求点:假设一个第三方库有提供了很多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

参考


网站公告

今日签到

点亮在社区的每一天
去签到