使用C++ 17 variant and visit实现 Multiple Dispatch

#include <fmt/format.h>

#include <string_view>
#include <variant>

struct SpaceObject {
  constexpr virtual ~SpaceObject() = default;
  [[nodiscard]] virtual constexpr auto get_name() const noexcept
      -> std::string_view = 0;
  int x;
  int y;
};

struct Craft : SpaceObject {
  [[nodiscard]] constexpr std::string_view get_name() const noexcept override {
    return "Craft";
  }
};
struct Asteroid : SpaceObject {
  [[nodiscard]] constexpr std::string_view get_name() const noexcept override {
    return "Asteroid";
  }
};

std::unique_ptr<SpaceObject> factory();

template <typename... Callable>
struct visitor : Callable... {
  using Callable::operator()...;
};

void collide(const Craft &, const Craft &) { std::puts("C/C"); }
void collide(const Asteroid &, const Asteroid &) { std::puts("A/A"); }
void collide(const Craft &, const Asteroid &) { std::puts("C/A"); }
void collide(const Asteroid &, const Craft &) { std::puts("A/C"); }

std::vector<std::variant<Craft, Asteroid>> get_objects();

void process_collisions(const std::variant<Craft, Asteroid> &obj) {
  for (const auto &other : get_objects()) {
    std::visit(
        [](const auto &lhs, const auto &rhs) {
          if (lhs.x == rhs.x && lhs.y == rhs.y) {
            collide(lhs, rhs);
          }
        },
        obj, other);
  }
}

  1. SpaceObject 基类
struct SpaceObject {
  constexpr virtual ~SpaceObject() = default;
  [[nodiscard]] virtual constexpr auto get_name() const noexcept
      -> std::string_view = 0;
  int x;
  int y;
};

SpaceObject: 这是一个抽象基类,定义了所有空间对象(如飞船和小行星)共有的特征。

constexpr virtual ~SpaceObject() = default;:

virtual: 指定析构函数是虚函数,确保在删除通过基类指针指向的派生类对象时,会调用派生类的析构函数。
constexpr: 表明这个析构函数可以在编译时被调用,尽管在这个上下文中,它的实际用途较少。constexpr 和 virtual 一起使用是为了支持在编译期和运行期的多态性。
= default;: 告诉编译器生成默认的析构函数实现。
[[nodiscard]] virtual constexpr auto get_name() const noexcept -> std::string_view = 0;:
[[nodiscard]]: 提醒调用者不要忽略返回值。
virtual: 指定 get_name 是一个虚函数,派生类必须实现它。
constexpr: 表示这个函数可以在编译时被计算,前提是它的调用环境允许。
noexcept: 表明这个函数不会抛出异常。
= 0;: 纯虚函数,必须在派生类中实现。
2. Craft 和 Asteroid 派生类

struct Craft : SpaceObject {
  [[nodiscard]] constexpr std::string_view get_name() const noexcept override {
    return "Craft";
  }
};
struct Asteroid : SpaceObject {
  [[nodiscard]] constexpr std::string_view get_name() const noexcept override {
    return "Asteroid";
  }
};

Craft 和 Asteroid: 这两个类继承自 SpaceObject,分别代表飞船和小行星。
get_name: 这两个派生类各自实现了基类的纯虚函数 get_name,返回各自的名称。因为 get_name 是 constexpr 函数,所以它们可以在编译时被调用。
3. visitor 模板

template <typename... Callable>
struct visitor : Callable... {
  using Callable::operator()...;
};

visitor 模板: 这个模板类是一个多重继承的工具,用于将多个可调用对象(如 lambda、函数对象等)组合成一个对象。using Callable::operator()…; 语句将所有基类的 operator() 引入当前作用域,从而使 visitor 可以通过一个对象调用多个可调用对象的 operator()。
4. collide 函数

void collide(const Craft &, const Craft &) { std::puts("C/C"); }
void collide(const Asteroid &, const Asteroid &) { std::puts("A/A"); }
void collide(const Craft &, const Asteroid &) { std::puts("C/A"); }
void collide(const Asteroid &, const Craft &) { std::puts("A/C"); }

collide 函数: 这些是处理不同类型对象碰撞的函数重载。当两个 Craft 或两个 Asteroid 碰撞时,分别调用不同的重载函数。对于 Craft 和 Asteroid 的混合碰撞,也有对应的重载函数。
5. process_collisions 函数

std::vector<std::variant<Craft, Asteroid>> get_objects();

void process_collisions(const std::variant<Craft, Asteroid> &obj) {
  for (const auto &other : get_objects()) {
    std::visit(
        [](const auto &lhs, const auto &rhs) {
          if (lhs.x == rhs.x && lhs.y == rhs.y) {
            collide(lhs, rhs);
          }
        },
        obj, other);
  }
}

get_objects(): 假定这个函数返回一个 std::vector,其中包含若干 std::variant<Craft, Asteroid> 对象,即飞船或小行星的列表。
process_collisions 函数:
接受一个 std::variant<Craft, Asteroid> 类型的参数 obj。
使用 for 循环遍历 get_objects() 返回的每一个 std::variant 对象 other。
对于每对 obj 和 other,使用 std::visit 来处理它们的类型并调用适当的 lambda 函数。
Lambda 函数:
lhs 和 rhs 是从 obj 和 other 中提取的对象,可能是 Craft 或 Asteroid。
如果两个对象的位置(x 和 y)相同,则调用相应的 collide 函数处理碰撞。
6. std::visit 的作用
std::visit: 通过传入的两个 std::variant(obj 和 other),std::visit 会自动匹配其中包含的类型,并将其传递给 lambda 函数。lambda 函数根据具体的类型进行处理,这种机制允许在运行时动态地处理多种可能的对象类型。
相较于对于每个元素进行动态转换判断实现了动态dispatch
在这里插入图片描述

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Variant 是一种可以存储不同类型数据的数据类型。在 C++ 中可以使用模板实现 Variant ,具体实现如下: ```cpp #include <memory> #include <type_traits> class Variant { public: Variant() noexcept = default; Variant(const Variant& other) { if (other._data) { other._data->copy_to(_data); _type_info = other._type_info; } } Variant& operator=(const Variant& other) { if (other._data) { other._data->copy_to(_data); _type_info = other._type_info; } else { _data.reset(); _type_info = nullptr; } return *this; } template <typename T> Variant(T&& value) { _data = std::make_unique<Holder<std::decay_t<T>>>(std::forward<T>(value)); _type_info = &typeid(std::decay_t<T>); } template <typename T> Variant& operator=(T&& value) { if (_data) { _data.reset(); } _data = std::make_unique<Holder<std::decay_t<T>>>(std::forward<T>(value)); _type_info = &typeid(std::decay_t<T>); return *this; } template <typename T> const T& get() const { if (_type_info && *_type_info == typeid(T)) { return static_cast<Holder<T>*>(_data.get())->value; } throw std::bad_cast(); } template <typename T> T& get() { if (_type_info && *_type_info == typeid(T)) { return static_cast<Holder<T>*>(_data.get())->value; } throw std::bad_cast(); } bool empty() const { return !_data; } private: class IHolder { public: virtual ~IHolder() = default; virtual void copy_to(std::unique_ptr<IHolder>& other) const = 0; }; template <typename T> class Holder : public IHolder { public: template <typename U> Holder(U&& value) : value(std::forward<U>(value)) {} void copy_to(std::unique_ptr<IHolder>& other) const override { other = std::make_unique<Holder<T>>(value); } T value; }; std::unique_ptr<IHolder> _data; const std::type_info* _type_info = nullptr; }; ``` 该实现中,使用了一个 IHolder 抽象基类来表示存储的数据,Holder 类是 IHolder 的具体实现,包含了存储的数据和拷贝操作。在 Variant 中,使用一个 std::unique_ptr<IHolder> 类型的成员变量 _data 存储数据,_type_info 存储数据的类型信息。 实现中还包括了拷贝构造函数、拷贝赋值运算符,并且支持使用任何类型来构造 Variant使用 get() 方法来获取存储的数据,并且支持空 Variant
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值