c++ 标准::功能:无法将派生类参数转换为基类参数

wfypjpf4  于 2023-04-01  发布在  其他
关注(0)|答案(1)|浏览(87)

我试图在C++(C++17)中模仿JavaScript风格的Event/EventTarget操作。我有一个Event基类,可以根据需要工作,还有一个KeyboardEvent类,继承自Event。我的EventTarget.addEventListener函数将EventObjects存储在一个可以用dispatchEvent调用的向量中。
我可以使用addEventListener来存储使用Event参数的回调函数,例如onGenericEvent(Event event)。但是,我无法使用addEventListener来添加接受KeyboardEvent参数的回调函数,例如onKeyDown(KeyboardEvent event)
我希望std::function<void(Event)>接受KeyboardEvent,因为它继承自Event,但我收到一个编译错误:
错误:无法将“void(*)(KeyboardEvent)”转换为“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

mcvgt66p

mcvgt66p1#

注意:首先,如果你像这样通过值传递Event对象,你将对它们进行切片。你需要传递引用或指针以使多态性工作!
void(KeyboardEvent&)void(Event&)更具体,所以std::function<void(Event&)>不能 Package 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参数。如果允许 Package void(KeyboardEvent&),则会导致传递给处理程序的类型错误。因此,您想要的是不允许的。
请记住,类型检查是在 * 编译时 * 完成的。仅仅因为你(试图)在运行时通过检查EventObjecttype成员而从不传递错误的类型,并不意味着你可以在编译时有不匹配的类型。
有两种方法可以解决这个问题:
1.让所有处理程序接受Event,并在运行时检查它们是否获得了预期的事件类型(例如,使用dynamic_cast)。
1.使用模板确保在编译时始终将正确类型的事件传递给正确的处理程序。
作为第二个选项的例子,你可以这样做:

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向量。现在编译器可以在编译时检查是否总是将正确的事件类型传递给每个处理程序。

相关问题