假设我有一个像这样的通用
Event
类:
// Event (Generic)
struct Event {
// Event type
std::string m_type;
// c-tor
Event(const std::string& type) : m_type(type) { /* empty */ }
// d-tor (polymorphic)
virtual ~Event() = default;
};
假设我有一个
KeyEvent
类,它的子类 Event
:
// Event (Keyboard)
struct KeyEvent : Event {
// Key
char m_key;
// Key down or up
bool m_down;
// c-tor
KeyEvent(char key, bool down)
: Event(down ? "keydown" : "keyup")
, m_key(key)
, m_down(down) { /* empty */ }
};
假设我有一个通用的
Component
类,它可以监听并触发上面定义的事件
// Component (listens to events)
class Component {
// ...
std::map<std::string, std::vector<std::function<void(const Event&)>>> m_events;
// ...
public:
// Listen to event
void listen(const std::string& type, std::function<void(const Event&)> callback) {
m_events[type].push_back(callback);
}
// Trigger event
void trigger(const Event& event) {
auto it = m_events.find(event.m_type);
if (it != m_events.end()) {
for (auto& callback : m_events[event.m_type]) {
callback(event);
}
}
}
};
我希望能够通过以下方式添加按键侦听器。
int main() {
Component c;
// Listen to key down event
c.listen("keydown", [](const KeyEvent& keyEvent){
std::cout << "You pressed '" << keyEvent.m_key << "'!\n";
});
// Trigger event
c.trigger(KeyEvent('A', true));
}
但是这会导致以下编译器错误
main.cpp: In function ‘int main()’:
main.cpp:65:13: error: cannot convert ‘main()::<lambda(const KeyEvent&)>’ to ‘std::function<void(const Event&)>’
65 | c.listen("keydown", [](const KeyEvent& event) {
| ~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
66 |
|
67 | });
| ~~
main.cpp:46:76: note: initializing argument 2 of ‘void Component::listen(const string&, std::function<void(const Event&)>)’
46 | void listen(const std::string& type, std::function<void(const Event&)> callback) {
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~
我可以通过使用
[](const Event&){ ... }
(而不是 [](const KeyEvent&){ ... }
)轻松解决问题,然后明确地将 dynamic_cast
转换为 Event
:KeyEvent
但是,我想知道是否有一种方法可以将
int main() {
Component c;
// Listen to event
c.listen("keydown", [](const Event& event){
auto& keyEvent = dynamic_cast<const KeyEvent&>(event);
std::cout << "You pressed '" << keyEvent.m_key << "'!\n";
});
// Trigger event
c.trigger(KeyEvent('A', true));
}
单独保留在
[](const KeyEvent&){ ... }
函数中,并修改 main()
的第二个参数,以便它毕竟允许这样的 lambda 函数?也许,我可以将回调参数包装在包装函数void Component::listen(const std::string&, std::function<void(const Event&)>)
中,它可以在将其添加到[](const Event&){ ... }
之前进行向下转换?m_events
这样您就可以进行这样的通话。
//template <std::derived_from<Event> T> only C++20
template <typename T>
void listen(std::string const& type, std::function<void(T)> callback) {
auto wrapper = [callback = std::move(callback)](Event const& ev) {
callback(static_cast<T const&>(ev));
};
m_events[type].push_back(std::move(wrapper));
}
请注意,使用 C++20 概念
c.listen<KeyEvent>("keydown", [](const KeyEvent& keyEvent){
// ^^^^^^^^
std::cout << "You pressed '" << keyEvent.m_key << "'!\n";
});
,您始终可以
std::derived_from
,而使用 C++17,如您的 OP 中标记的那样,您可能仍想使用 static_cast
并弄清楚如果有人打电话该怎么办您的 dynamic_cast
函数没有派生自 listen
的类型。