2016-09-16 66 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" 
}