c++ - 通过 std::variant 而不是继承进行多态时减少冗长的技术

标签 c++ composition std-variant

假设您在 2D 游戏框架或类似的东西中有实体——例如一个 GUI 框架——其中有各种类型的实体共享位置和旋转等公共(public)属性,但其中一些属性必须在每个实体类型的基础上处理,例如旋转一个简单的 Sprite 与旋转一个绑定(bind)的 2D 动画骨架不同。

显然,这可以通过传统的 OOP 继承层次结构来处理......但是,我感兴趣的是使用“组合继承”来表示此类实体,方法是拥有一个没有继承任何内容的具体类,称为 actor它具有状态的 Vanilla 成员变量,在实体类型之间以相同的方式处理,但也与包含必须以每个实体类型的方式处理的状态的变体具有关系。

此设计可以按如下方式工作:

#include <iostream>
#include <variant>

struct actor_state_1 {
    float rotation_;
    //point position;
    // etc...

    void set_rotation(float theta) {
        rotation_ = theta;
        // say there are different things that need to happen here 
        // for different actor types...
        // e.g. if this is an animation skeleton you need to find the
        // the root bone and rotate that, etc. 
        std::cout << "actor_state_1 set_rotation\n";
    }

    void set_position(const std::tuple<float, float>& pos) {
        // etc ...
    }

    float get_rotation() const {
        return rotation_;
    }

    // get_position, etc...
};

struct actor_state_2 {
    float rotation_;

    void set_rotation(float theta) {
        rotation_ = theta;
        std::cout << "actor_state_2 set_rotation\n";
    }

    void set_position(const std::tuple<float, float>& pos) {
        // etc ...
    }

    float get_rotation() const {
        return rotation_;
    }

    // get_position, etc...
};

using state_variant = std::variant<actor_state_1, actor_state_2>;

class actor {
private:
    state_variant state_;
    // common properties...
    float alpha_transparency; // etc.
public:

    actor(const actor_state_1& state) :
        state_(state)
    {}

    actor(const actor_state_2& state) :
        state_(state)
    {}

    void rotate_by(float theta) {
        auto current_rotation = get_rotation();
        std::visit(
            [current_rotation, theta](auto& a) { a.set_rotation(current_rotation + theta); },
            state_
        );
    }

    float get_rotation() const {
        return std::visit(
            [](const auto& a) {return a.get_rotation(); },
            state_
        );
    }

    void move_by(const std::tuple<float, float>& translation_vec); 
    std::tuple<float, float> get_postion() const; // etc.

};

int main() {
    auto a = actor(actor_state_2{ 90.0f });
    a.rotate_by(45.0f);
    std::cout << a.get_rotation() << "\n";
}

但是,我觉得冗长的程度和每个属性重复的样板代码的数量使这样的设计变得笨拙。但是,我想不出一种使用模板来减少样板文件的方法。似乎应该有一种方法至少可以为“传递 setter/getter ”制作一个模板,比如 actor::get_rotation()在上面,但我不知道对成员函数进行参数化的方法,这似乎是您需要做的。

有没有人对像这样的设计不那么冗长或使用较少的样板文件有想法?

最佳答案

只需使用访问者:

#include <variant>

struct state_a{};
struct state_b{};

struct actor
{
   std::variant<state_a, state_b> state;
};

// basically no need to declare them as members.
void rotate(state_a, double deg)
{
  // do a
}

void rotate(state_b, double deg)
{
  // do b
}

void rotate(actor& a, double deg)
{
  std::visit([deg](auto &state){ rotate(state, deg); }, a.state);
}

这是一个带有模板的更具扩展性的版本: https://godbolt.org/z/58Kjx9h5W

// actor.hpp
#include <variant>

template <typename ... StatesT>
struct actor
{
   std::variant<StatesT...> state;
};

template <typename StateT>
void rotate(StateT& s, double deg)
{
    return rotate(s, deg);
}

template <typename ... StatesT>
void rotate(actor<StatesT...> &a, double deg)
{
    std::visit([deg](auto &state){ rotate(state, deg); }, a.state);
}

// other headers
#include <iostream>

struct state_a{};
struct state_b{};

// basically no need to declare them as members.
void rotate(state_a, double deg)
{
    // do a
    std::cout << "rotating a\n";
}

void rotate(state_b, double deg)
{
    // do b
    std::cout << "rotating a\n";
}

int main()
{
    auto actr = actor<state_a, state_b>{state_a()};
    rotate(actr, 30);
}

但是请注意,“recursivetemplate <typename StateT> void rotate(StateT& s, double deg)如果您因为 ADL 而对您的状态使用命名空间,则将不起作用。
您要么声明 rotatestate_a在同一个命名空间中。

关于c++ - 通过 std::variant 而不是继承进行多态时减少冗长的技术,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70268201/

相关文章:

scala - 函数组合,累积中间结果

c++ - 派生自并合成相同的类。 C++

scala - 如何使用规范测试框架从 Matcher[A] 组成 Matcher[Iterable[A]]

c++ - 如何在带有访问者的lambda中调用std::visit,访问者是按值捕获的函数对象

c++ - 我如何编码类似std::variant的开关?

c++ - 是否可以使用 C++ 模板函数来接受任何类型 T 的集合?

c++ - 通过命令行编译C/C++/Visual C程序

c++ - cout 后增量的行为

c++ - destroyWindow(来自 opencv)关闭所有窗口并停止 c++ 程序

c++ - 为什么与比较类不在同一个命名空间中时,std::variant无法找到operator <()