2016-09-16 67 views
3

我正在学习C++,并希望构建类似于C#事件的东西来处理嵌入式C++项目中的中断。C++多态性:我错过了什么?

到目前为止,我想出了一个几乎可以实现我想要的解决方案。不过,我需要一些关于多态性(?)的帮助。下面的代码片段是一种最小的例子重现我的情况:

#include <iostream>  

struct Event 
    { }; 

struct EventHandler 
    { 
    virtual void Esr (const Event& I) { } 
    }; 

struct EventSender 
    { 
    EventSender (EventHandler& Handler) : _Handler (Handler) { } 

    template <typename T> 
    void SendEvent (const T&) const 
     { 
     _Handler.Esr (T()); 
     } 

    EventHandler& _Handler; 
    }; 

struct SpecialEvent : public Event 
    { }; 

struct MyHandler : public EventHandler 
    { 
    void Esr (const Event& I) override { std::cout << "Event" << std::endl; } 
    void Esr (const SpecialEvent& I) { std::cout << "SpecialEvent" << std::endl; } 
    };    

int main() 
    { 
    MyHandler handler; 
    EventSender sender (handler); 

    /* Invoke directly */ 
    handler.Esr (Event()); 
    handler.Esr (SpecialEvent()); 

    /* Invoke indirectly */ 
    sender.SendEvent (Event()); 
    sender.SendEvent (SpecialEvent()); // Expected cout msg: "SpecialEvent" 

    return 0; 
    } 

预期的控制台输出:

Event 
SpecialEvent 
Event 
SpecialEvent 

实际控制台输出:

Event 
SpecialEvent 
Event 
Event 

什么编译器/连接器在这里,我不知道?

+0

不知道这是有关这个问题,但为什么你传递事件实例'SendEvent'和那么在'SendEvent'里面忽略这个参数,但是一个新的'Event'实例被传递给'Esr'? – user463035818

+0

解决这些问题的正确工具是您的调试器。在*堆栈溢出问题之前,您应该逐行执行您的代码。如需更多帮助,请阅读[如何调试小程序(由Eric Lippert撰写)](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/)。至少,您应该\编辑您的问题,以包含一个[最小,完整和可验证](http://stackoverflow.com/help/mcve)示例,该示例再现了您的问题,以及您在调试器。 –

+0

一个有趣的问题:向答案迈进一步,但不是一个完整的答案,因此是一个评论:通过替换{} is = 0使Esr方法变为纯虚拟。在方法声明中。 –

回答

2

在MyHandler中有两种方法。其中一个覆盖基类方法 另一个没有。

一个解决办法是声明在基类两种方法:

struct EventHandler 
{ 
    virtual void Esr (const Event& I) = 0; 
    virtual void Esr (const SpecialEvent& I) = 0; 
}; 

这样,编译器可以使用该参数的类型,以解决在事件处理程序水平的方法。

如果你想避免所有派生类必须重载这两种方法,你可以做这样的事情的要求:

struct EventHandler 
{ 
    virtual void Esr (const Event& I) = 0; 
    virtual void Esr (const SpecialEvent& I) 
    { 
     // if not overridden, use the non-specialized event handler. 
     Esr(reinterpret_cast<const Event &>(I)); 
    } 
}; 

要回答你的问题:

什么编译器/连接器在这里,我不知道?

在C++方法调用在编译/链接时进入任一1)至一个特定的代码块(方法体)的呼叫时,或2)通过一个隐藏的数据结构的间接调用被称为虚函数表已经解决。实际的vtable是在运行时确定的,但编译器必须决定表中哪个条目用于该调用。 (谷歌vtable有关更多关于它们是什么以及它们如何实现的更多信息。)

它必须以这个决议的基础知道它允许的内容。在这种情况下,基于调用该方法的指针或引用的类型。请注意,这不一定是实际对象的类型。

在当你打电话给你的情况下throgh handler编译器被允许了解一下MyHandler声明,因此可以选择你所期望的一个两种方法,但是当呼叫经过sender,它必须找到EventSender声明的方法。在EventSender中只有一种方法。幸运的是,参数可以强制为const Event &,因此编译器可以使用该方法。因此它使用该方法的vtable条目。所以它找到虚函数表的MyHandler [运行时]并使用

Esr (const Event& I) 

你如何在错误的方法,最终的V表项。

顺便说一句:我的答案旨在解释你所看到的并给你一种解决你眼前的问题的方法。杰里科芬的答案为您提供了一种长期适合您的替代方法。

1

首先,您不能将引用强制转换为基类的后代。 您需要使用指向该类型的指针,并使用dynamic_cast

所以,你必须

EventSender sender (handler); 
main()

sender的构造函数绑定到MyHandler的基类,这是EventHandler,因为这是MyHandler(= EventHandler::EventHandler)的构造函数中的参数类型。因此,EventHandler.Esr(const Event &)被调用,这恰好是虚拟的,所以有一个指向MyHandler.Esr(const Event &)的指针。

请注意,在技术上,Esr(const Event &)Esr(const SpecialEvent &)是两种不同的方法;他们恰好碰巧使用了相同的名字。

3

这里您试图使用重载,而不是经典(基于虚函数)多态。

你想要的东西(至少据我了解)是直接使用handler,并通过sender间接调用它的行为。发生的变化是在EventSpecialEvent之间。

既然如此,经典多态性将涉及Event一个虚拟函数在SpecialEvent覆盖:

struct Event { 
    virtual void operator()() const { std::cout << "Event\n"; } 
}; 

struct SpecialEvent : public Event { 
    virtual void operator()() const override { std::cout << "Special Event\n"; } 
}; 

有了这个地方,参考(或指针),以一个Event将调用用于所述构件实际类型。在这里做的多态是指,我们只需要一个处理程序类,所以代码最终是这样的:

#include <iostream> 

struct Event { 
    virtual void operator()() const { std::cout << "Event\n"; } 
}; 

struct EventHandler { 
    void Esr(const Event& I) const { I(); } 
}; 

struct EventSender { 
    template <typename T> 
    void SendEvent (const T& t) const { 
     handler.Esr(t); 
    } 

    EventHandler handler; 
}; 

struct SpecialEvent : public Event { 
    virtual void operator()() const override { std::cout << "Special Event\n"; } 
}; 

int main() { 
    EventHandler handler; 
    EventSender sender; 

    /* Invoke directly */ 
    handler.Esr (Event()); 
    handler.Esr (SpecialEvent()); 

    /* Invoke indirectly */ 
    sender.SendEvent (Event()); 
    sender.SendEvent (SpecialEvent()); // Expected cout msg: "SpecialEvent" 
}