当通过std::variant而不是继承进行多态性时,减少冗长的技术



假设你在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_rotationn";
}
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_rotationn";
}
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(),但我不知道如何对成员函数进行参数化,这似乎是你需要做的。

有没有人有这样一个设计的想法,不那么冗长或使用更少的样板?

只需使用访问者:

#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 an";
}
void rotate(state_b, double deg)
{
// do b
std::cout << "rotating an";
}
int main()
{
auto actr = actor<state_a, state_b>{state_a()};
rotate(actr, 30);
}

然而,请注意;递归的";如果由于ADL而为状态使用名称空间,template <typename StateT> void rotate(StateT& s, double deg)将不起作用
在同一命名空间中声明rotatestate_a

最新更新