事件系统:使用类型转换或联合进行继承
Event system: Inheritance with type-casting or unions
我目前正在为我的游戏引擎开发一个事件系统。我想过两种实现它的方法:
1.继承:
class Event
{
//...
Type type; // Event type stored using an enum class
}
class KeyEvent : public Event
{
int keyCode = 0;
//...
}
2.与工会:
class Event
{
struct KeyEvent
{
int keyCode = 0;
//...
}
Type type; // Event type stored using an enum class
union {
KeyEvent keyEvent;
MouseButtonEvent mouseButtonEvent;
//...
}
}
在这两种情况下,您都必须检查Event::Type
枚举,以了解发生了哪种事件。
使用继承时,必须强制转换事件以获取其值(例如,将Event
转换为KeyPressedEvent
以获取密钥代码(。因此,您必须将原始指针转换为 neeeded 类型,这通常非常容易出错。
使用联合时,只需检索已发生的事件即可。但这也意味着内存中联合的大小总是与其中包含的最大可能类一样大。这意味着可能会浪费大量内存。
现在来问问题:
是否有任何论据可以使用一种方式而不是另一种方式?我应该浪费内存还是冒错选的风险?
还是有我没有想到的更好的解决方案?
最后,我只想传递例如KeyEvent
作为Event
。然后我想使用枚举类检查Event::Type
。如果类型等于Event::Type::Key
我想获取密钥事件的数据。
>Event
似乎更以数据为中心而不是以行为为中心,所以我会选择std::variant
:
using Event = std::variant<KeyEvent, MouseButtonEvent/*, ...*/>;
// or using Event = std::variant<std::unique_ptr<KeyEvent>, std::unique_ptr<MouseButtonEvent>/*, ...*/>;
// In you are concerned to the size... at the price of extra indirection+allocation
那么用法可能类似于
void Print(const KeyEvent& ev)
{
std::cout << "KeyEvent: " << ev.key << std::endl;
}
void Print(const MouseButtonEvent& ev)
{
std::cout << "MouseButtonEvent: " << ev.button << std::endl;
}
// ...
void Print_Dispatch(const Event& ev)
{
std::visit([](const auto& ev) { return Print(ev); }, ev);
}
不同类型的事件通常包含非常不同的数据,并且具有非常不同的用法。我会说这不适合子类型(继承(。
让我们先弄清楚一件事:在这两种情况下,您都不需要type
字段。在继承的情况下,您可以通过强制转换来确定当前对象的子类。在指针上使用时,dynamic_cast
返回正确子类的对象或 null 指针。关于 cpp 偏好有一个很好的例子。在标记联合的情况下,您实际上应该使用表示标记联合的类,例如std::variant
,而不是在联合旁边有一个type
作为类的成员。
如果要使用继承,则典型的方法是将事件放在Event
指针的数组(向量(中,并从Event
类提供的接口使用它们执行操作。但是,由于这些是事件,您可能不会根据事件类型执行相同的操作,因此您最终会将当前Event
对象向下转换为其中一个子类(KeyEvent
(,然后才从子类提供的接口中使用它(以某种方式使用keyCode
(。
但是,如果要使用标记的联合或变体,则可以直接将Event
对象放在数组中,而不必处理指针和向下转换。当然,您可能会浪费一些内存,但您的事件类到底有多大?此外,除了不必取消引用指针之外,您还可能仅仅因为Event
对象在内存中靠得很近而获得性能。
同样,我会说变体比子类型更适合这种情况,因为不同类型的事件通常以非常不同的方式使用。例如,一个KeyEvent
有一个在数百个中关联的键("键代码"(,并且没有位置,而一个MouseEvent
在三个(可能是五个,但不是更多(中有一个关联的按钮,以及一个位置。我想不出两者之间的共同行为,除了非常一般的东西,如时间戳或哪个窗口接收事件。
你可以使用多态性,我相信这是一个解决方案,如果你的派生类共享大量的方法,你可以有一个抽象的事件类,从中派生并实现这些方法,然后你可以将它们放在同一个容器中,如果操作正确,你不需要强制转换。
大致如下:
现场演示
#include <vector>
#include <iostream>
#include <memory>
class Event {//abstract class
public:
virtual void whatAmI() = 0; //pure virtual defined in derived classes
virtual int getKey() = 0;
virtual ~Event(){}
};
class KeyEvent : public Event {
int keyCode = 0;
public:
void whatAmI() override { std::cout << "I'm a KeyEvent" << std::endl; }
int getKey() override { return keyCode; }
};
class MouseEvent : public Event {
int cursorX = 0, cursorY = 0;
public:
void whatAmI() override { std::cout << "I'm a MouseEvent" << std::endl; }
int getKey() override { throw -1; }
};
int main() {
std::vector<std::unique_ptr<Event>> events;
events.push_back(std::make_unique<KeyEvent>()); //smart pointers
events.push_back(std::make_unique<MouseEvent>());
try{
for(auto& i : events) {
i->whatAmI();
std::cout << i->getKey() << " Key" << std::endl;
}
} catch(int i){
std::cout << "I have no keys";
}
}
输出:
I'm a KeyEvent
0 Key
I'm a MouseEvent
I have no keys
这是一个简化版本,您仍然需要处理复制构造函数、赋值运算符等。
- 有关插入适配器的错误。[错误]请求从 'back_insert_iterator<vector<>>' 类型转换为非标量类型
- 处理小于cpu数据总线的数据类型.(c++转换为机器代码)
- C++中的双指针类型转换
- 逐位操作的隐式类型转换
- 模板中的类型转换
- 在 C++(和 C)中进行类型转换时明显不一致
- 字符类型转换不兼容
- 将复杂的非基元C++数据类型转换为 Erlang/Elixir 格式,以使用 NIF 导出方法
- C++:用户定义的显式类型转换函数错误
- 事件系统:使用类型转换或联合进行继承
- 继承和隐式类型转换
- 继承结构的类型转换导致g++编译错误
- 继承类型的指针之间的静态强制转换
- QObject基类到继承对象的C++类型转换
- 为什么显式类型转换允许对私有继承进行向上转换
- C++中的类型转换和继承
- 多重继承c++,如何进行类型转换
- 多重继承和强制类型转换
- 为什么类型转换运算符不适用于继承的类?
- 基于继承的c++强制类型转换