3

2D ゲーム フレームワークまたは同様のもの (GUI フレームワークなど) にエンティティがあるとします。ここでは、位置や回転などの共通のプロパティを共有するさまざまなタイプのエンティティが存在しますが、これらのプロパティの一部はエンティティごとのタイプで処理する必要があります。たとえば、単純なスプライトの回転は、リグ付きの 2D アニメーション スケルトンの回転とは異なる方法で実行されます。

明らかに、これは従来の OOP 継承階層によって処理できます...ただし、代わりに、呼び出し元から何も継承されずactor、状態のバニラメンバー変数を持つ具体的なクラスを持つことにより、「継承よりも構成」を使用してそのようなエンティティを表すことに興味があります。エンティティ タイプ間で同じ方法で処理されますが、エンティティ タイプごとに処理する必要がある状態を含むバリアントとの has-a 関係もあります。

この設計は、次のように機能させることができます。

#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";
}

しかし、冗長性のレベルと、プロパティごとに繰り返される定型コードの量が、そのような設計を扱いにくくしているように感じます。ただし、テンプレートを使用してボイラープレートを削減する方法がわかりません。上記のように、少なくとも「パススルーゲッター」のテンプレートを作成する方法があるはずですがactor::get_rotation()、メンバー関数でパラメーター化する方法がわかりません。これは、あなたがする必要があるようです.

このような冗長性を減らしたり、ボイラープレートの使用を減らしたりするためのアイデアを持っている人はいますか?

4

1 に答える 1

2

ビジターを使用するだけです:

#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);
}

ただし、ADL のために状態に名前空間を使用する場合、「 recursive」は機能しないことに注意してください。同じ名前空間でANDを 宣言します。template <typename StateT> void rotate(StateT& s, double deg)
rotatestate_a

于 2021-12-07T23:37:41.237 に答える