2017-03-11 77 views
2

所以。C++ - 如何使用基指針重寫子類方法

我正在爲學校的一個小遊戲項目工作,我碰到了碰撞和東西爲我的對象工作,並且想法是檢查一個對象是否與另一個對象碰撞,然後讓每個類型的具體邏輯有一堆例如,如果對象是一個玩家控制的對象,敵人會傷害它,但如果它是一個加電與敵人相撞,事情會很好。

所以,我希望我可以做這樣的事情(一切從繼承的OBJ明顯):

std::vector<Obj*> list; 
list.push_back(new Powerup()); 
list.push_back(new Enemy()); 
list.push_back(new Player()); 

for (auto i: list) { 
    for (auto j: list) { 
     if (collision(i,j)) { 
      i->doStuff(*j); 
     } 
    } 
} 

但是我無法找到一種方法在適當的類型來發送。我做了一個測試程序,證明了問題:

#include <iostream> 

class A { 
    public: 
     virtual void doStuff(A& T) { std::cout << "A->A!" << std::endl; } 
}; 

class B : public A { 
    public: 
     virtual void doStuff(A& T) { std::cout << "B->A!" << std::endl; } 
}; 

class C : public A { 
    public: 
     virtual void doStuff(A& T) { std::cout << "C->A!" << std::endl; } 
     virtual void doStuff(B& T) { std::cout << "C->B!" << std::endl; } 
}; 

int main() { 
    A* base; 
    A a; 
    B b; 
    C c; 

    c.doStuff(a); 
    c.doStuff(b); 

    base = &a; 
    c.doStuff(*base); 

    base = &b; 
    c.doStuff(*base); 

    return 0; 
} 

並運行它,我得到這個:

C->A! 
C->B! 
C->A! 
C->A! 

當我期待:

C->A! 
C->B! 
C->A! 
C->B! 

任何想法如何使這項工作?

回答

0

問題是,您最後一次撥打doStuff您通過A&,因此它會調用C::doStuff(A&)。如果您想要撥打C::doStuff(B&),您需要將base轉換爲B&。這可以使用static_castdynamic_cast來完成。

你應該看看雙調度:Understanding double dispatch C++

+0

好,所以我認爲我必須這樣做: void doStuff(A&T)A * a = dynamic_cast (T); B * b = dynamic_cast (T); if(a){ std :: cout << "C-> A!「<< std :: endl; } else if(b){ std :: cout << "C-> B!」 << std :: endl; } } 爲了得到想要的結果呢? 或者,我在主程序中執行if語句,但隨後我必須更改每個新類的主程序...:/ –

+0

研究雙重調度。這是你想要的,如果你不想爲每種派生類型寫劇情。只有當你不知道B派生A時,動態轉換纔是有用的。如果B不派生A,它將返回'NULL' /'nullptr'。這是一個緩慢的運行時操作。如果你知道B派生出A,那麼使用'static_cast',否則你會得到UB。 – Gambit

+0

明白了,謝謝! –

3

什麼您正試圖在這裏被稱爲雙重分派 - 使虛擬相對於兩個參數的函數。與大多數具有虛擬功能的編程語言一樣,C++本身不支持這種功能。


考慮撥打c.doStuff(*base);。它真的有兩個參數引擎蓋下:c(其結果爲*this裏面的功能)和*base

現在的問題是doStuff是虛擬的,只關於第一個參數。這就是爲什麼在CdoStuff函數中,即使c有一個靜態的基類類型,調用最終也會結束。然而,第二個參數不是以多態的方式處理的。 base的靜態類型是A*,即使它當前指向子類對象,因此*base的結果爲A,並且該結果與A&過載相匹配。


儘管確實C++不支持雙重調度,但有多種方法來模擬它。訪客設計模式通常被認爲是實現這一目標的一種方式。例如,請參見Difference betwen Visitor pattern & Double Dispatch


P.S.:混合覆蓋與重載很少是一個好主意,因爲作爲代碼的人類讀者,找出哪個函數會被調用會變得非常混亂。

+0

是的,終於明白了,謝謝! –

0

問題是,C++使用單個調度,你想要的是雙派遣。一種說法是,單個調度使用編譯時可用的信息(將類型A的指針傳遞給函數),而不是運行時已知的信息(類型A的指針恰好指向類型B的對象所以當你決定調用一個函數的過載時,你想調用B)的重載。

一些好的資源在此:http://members.gamedev.net/sicrane/articles/dispatch.htmlhttps://en.wikipedia.org/wiki/Double_dispatch

的一種方式單牒的編程語言,通常去解決這個問題,沒有鑄造使用「訪問者模式」。

class DoStuffClass{ //visitor class 
public: 
    void doStuff(B &b);//traditionally visit(B &b) 
    void doStuff(C &c); 
    void doStuff(A &a); 
}; 

class A{ //visited class 
public: 
    void interactWith(DoStuffClass &dsc){ 
     //here's the good part: 
     //Now it is known at compile time(statically) what the type of *this is, 
     //so dispatching to the desired function - doStuff(A&) - can happen 
     doc.doStuff(*this); 
    }; 
} 

class B: public A{ //visited class 
public: 
    void interactWith(DoStuffClass &dsc){ 
     //same as for A 
     doc.doStuff(*this); 
    }; 
} 

注:傳統的訪問類成員類被稱爲訪問(SomeVisitedClass &)和訪問類的成員函數被調用接收器(遊客&)。但是,不管你喜歡,你都可以稱它。

然後,你可以這樣做:

DoStuffClass dsf; 
A a; 
B b; 
A *aptr; 
aptr = &b; 
a.interactWith(dsf); //will call the do stuff function that handles A 
b.interactWith(dsf); //will call the do stuff function that handles B 
aptr->interactWith(dsf);//still calls the do stuff function that handles B 

現在,當你在它,你也可以看看的調解模式。它的目的是從具有使每個班級知道所有其他的緩解需要了解對方的工作(如你的情況),並節省您的類之間的依賴關係:

https://en.wikipedia.org/wiki/Mediator_pattern

+0

呃,應該在提交之前刷新頁面:P Christian已經提到了我所說的部分內容。不過,您可能會發現閱讀第一個參考文獻和最後一個(關於介體模式)很有意思。 – Edd

+0

謝謝你的幫助,我設法用這裏的3個答案解決它。 :) –

+0

很高興聽到隊友。如果您發現這個答案(和/或Gambit's)有用,Stack Overflow會在答案旁邊提供向上箭頭以將其標記爲有用。隨意點擊。 :) – Edd