c++ - std::function:无法将派生类参数转换为基类参数

标签 c++ parameters dom-events c++17 std-function

我正在尝试模仿 JavaScript 风格 Event/EventTarget C++ (C++17) 中的操作。 我有一个Event根据需要工作的基类,以及 KeyboardEvent继承自 Event 的类。 我的EventTarget.addEventListener功能商店EventObjects在可以用 dispatchEvent 调用的 vector 中.

我可以使用addEventListener使用 Event 存储回调函数参数,例如onGenericEvent(Event event) 。 但是,我无法使用addEventListener添加采用 KeyboardEvent 的回调函数参数,例如onKeyDown(KeyboardEvent event) .

我在期待std::function<void(Event)>接受KeyboardEvent因为它继承自 Event但我收到编译错误:

ERROR: cannot convert 'void (*)(KeyboardEvent)' to 'std::function<void(Event)>'

我应该如何进行?

#include <iostream>
#include <functional>
#include <string>
#include <vector>


class Event {
public:
    Event(std::string _type, void* _pointer) {
        type = _type;
        pointer = _pointer;
    }
    std::string type;
    void* pointer;
};


struct EventObject {
    std::string type;
    std::function<void(Event)> listener;
    void* pointer;
};

class EventTarget {
public:
    static void addEventListener(std::string type, std::function<void(Event)> listener, void* pointer){
        EventObject eventObj;
        eventObj.type = type;
        eventObj.listener = listener;
        eventObj.pointer = pointer;
        events.push_back(eventObj);
    }
    static void dispatchEvent(Event event){
        for (unsigned int n = 0; n < events.size(); ++n) {
            if (event.type == events[n].type && event.pointer == events[n].pointer) {
                events[n].listener(event);
            }
        }
    }
    static std::vector<EventObject> events;
};
std::vector<EventObject> EventTarget::events;


class KeyboardEvent : public Event {
public:
    KeyboardEvent(std::string _type, void* _pointer) : Event(_type, _pointer) {
        key = "random key";
    }
    std::string key;
};


class App
{
public:
    static void onGenericEvent(Event event) {
        printf("event.type = %s \n", event.type.c_str());
    }
    
    static void onKeyDown(KeyboardEvent event) {
        printf("event.type = %s \n", event.type.c_str());  // print Event class property
        printf("event.key = %s \n", event.key.c_str());    // print KeyboardEvent class property
    }
};


int main() {
    App* app = new App();
    
    EventTarget::addEventListener("generic", &App::onGenericEvent, app);
    Event event("generic", app);
    EventTarget::dispatchEvent(event); // simulate generic event
    
    EventTarget::addEventListener("keydown", &App::onKeyDown, app);  //ERROR: cannot convert 'void (*)(KeyboardEvent)' to std::function<void(Event)>'
    KeyboardEvent keyEvent("keydown", app);                         
    EventTarget::dispatchEvent(keyEvent);  // simulate keydown event
}

在线测试代码:https://onlinegdb.com/uJBz6g4lr

最佳答案

注意:首先,您要对 Event 进行切片对象,如果你像这样按值传递它们。您需要传递引用或指针才能使多态性发挥作用!


void(KeyboardEvent&)void(Event&)更具体 ,所以std::function<void(Event&)>无法包裹 void(KeyboardEvent&) .

考虑一下如果可以的话,那么这是可能的:

void handler(KeybaordEvent& evt)
{
    std::cout << evt.key;
}

int main()
{
    std::function<void(Event&)> callback(&handler);
    MouseEvent mouseEvent;  // Imagine MouseEvent also derives from Event
    callback(mouseEvent);
}

MouseEventEvent一个std::function<void(Event&)>可以接受MouseEvent范围。如果允许包裹 void(KeyboardEvent&)这将导致处理程序传递错误的类型。因此,你想要的东西是不被允许的。

请记住,类型检查是在编译时完成的。只是因为您(尝试)通过检查 EventObject 永远不会传递错误的类型的type运行时的成员并不意味着您可以在编译时拥有不匹配的类型。


有两种方法可以解决这个问题:

  1. 让您的所有处理程序接受 Event并进行运行时检查,确保他们获得了预期的事件类型(即使用 dynamic_cast )。
  2. 使用模板确保在编译时始终将正确类型的事件传递给正确的处理程序。

作为第二个选项的示例,您可以这样:

template <typename EventType>
struct EventObject {
    std::string type;
    std::function<void(EventType)> listener;
    void* pointer;
};

class EventTarget {
public:
    template <typename EventType>
    static void addEventListener(std::string type, std::function<void(EventType)> listener, void* pointer){
        EventObject<EventType> eventObj;
        eventObj.type = type;
        eventObj.listener = listener;
        eventObj.pointer = pointer;
        events<EventType>.push_back(eventObj);
    }
    
    template <typename EventType>
    static void dispatchEvent(EventType event){
        for (auto& eventObj : events<EventType>) {
            eventObj.listener(event);
        }
    }
    
    template <typename EventType>
    static std::vector<EventObject<EventType>> events;
};

template <typename EventType>
std::vector<EventObject<EventType>> EventTarget::events;


class KeyboardEvent : public Event {
public:
    KeyboardEvent(std::string _type, void* _pointer) : Event(_type, _pointer) {
        key = "random key";
    }
    std::string key;
};


class App
{
public:
    static void onGenericEvent(Event event) {
        printf("event.type = %s \n", event.type.c_str());
    }
    
    static void onKeyDown(KeyboardEvent event) {
        printf("event.type = %s \n", event.type.c_str());  // print Event class property
        printf("event.key = %s \n", event.key.c_str());    // print KeyboardEvent class property
    }
};


int main() {
    App* app = new App();
    
    EventTarget::addEventListener<Event>("generic", &App::onGenericEvent, app);
    Event event("generic", app);
    EventTarget::dispatchEvent(event); // simulate generic event
    
    EventTarget::addEventListener<KeyboardEvent>("keydown", &App::onKeyDown, app);  //ERROR: cannot convert 'void (*)(KeyboardEvent)' to std::function<void(Event)>'
    KeyboardEvent keyEvent("keydown", app);                         
    EventTarget::dispatchEvent(keyEvent);  // simulate keydown event
}

Demo

这会创建单独的events每种类型事件的 vector 。现在,编译器可以在编译时检查是否始终将正确的事件类型传递给每个处理程序。

关于c++ - std::function:无法将派生类参数转换为基类参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75845709/

相关文章:

c# - 传递可变数量的泛型类型参数

MySQL存储过程在参数中传递数字数组

javascript - 拒绝 keyDown 事件上的 "control"键

html - 具有高 dpi (150%) 的 CHtmlView 中的网页显示不正确

c++ - 是否可以在一个类中包含另一个类中的类对象数组?

c++ - 如何让类(class)更有凝聚力?

javascript - stopImmediatePropagation 在 "document"和 "p"元素中具有不同的结果

c++ - 适用于存储和计算最高得分 K 项的数据结构

java - 向 Controller 发送参数

javascript - 在Javascript中,如何判断和调用继承的监听器?