2014-12-18 21 views
2

比方說,我有2個班InstrumentBrass,其中BrassInstrument得出:取消引用upcasted成員函數指針

class Instrument 
{ 
protected: 
std::string _sound; 
public: 
Instrument(std::string sound) : _sound(sound) {} 
virtual void play() { std::cout << _sound << std::endl; } 
}; 

class Brass : public Instrument 
{ 
private: 
std::string _pitchShifter; 
public: 
Brass(std::string pitchShifter) : Instrument("braaaaaa"), _pitchShifter(pitchShifter) 
{} 

void printPitchShifter() { std::cout << _pitchShifter << std::endl; } 
} 

對於一些瘋狂的原因,我有一個指針儀表的成員函數:

typedef void(Instrument::*instrumentVoidFunc)() //declaring member function pointers is too damn confusing 
instrumentVoidFunc instrumentAction; 

現在很明顯,這將工作,具有良好定義的行爲:

Instrument soundMaker("bang!"); 
instrumentAction = &Instrument::play; 
(soundMaker.*instrumentAction)(); 

而輸出應該是bang!

但我也可以上溯造型吧,像這樣讓instrumentAction指向一個黃銅成員函數出現在Instrument

instrumentAction = static_cast<instrumentVoidFunc>(&Brass::printPitchShifter); 

我的理解是(或者是,無論如何)是上溯造型的成員函數指針應該銷燬任何能夠引用基類中尚未存在的派生類函數的能力。但是:

Brass trumpet("valves"); 
(trumpet.*instrumentAction)(); 

...打印出valves就像我曾呼籲派生類的功能正常。因此,顯然上傳一個派生類函數指針不會影響在派生類上取消引用時發生的情況(儘管在基類上取消引用會導致未定義的行爲)。

編譯器如何使這成爲可能?

+0

我沒有給你答案,只是想說C++函數指針是可怕的。 – zmbq 2014-12-18 22:51:27

+3

一般來說,小心不要將實證結果與正確的行爲混爲一談。特別是C++有許多不同類型的未定義(或實現定義的)行爲,這些行爲會導致從不正確的代碼中看似正確的事情(通常這純粹是巧合)。 – Cameron 2014-12-18 22:52:45

+0

它聽起來像你正在手動實施一個vtable? – 2014-12-18 22:57:21

回答

3

雖然函數指針強制轉換是可能的,但通過與原始類型不匹配的函數類型調用函數是未定義的行爲。允許函數指針強制轉換的原因是支持強制轉換並存儲通用類型,但在調用函數指針之前通過強制轉換函數指針來恢復正確。這個限制的基本背景是即使兼容的指針可能需要調整使用。隱藏正確的簽名意味着存在合適的蹦牀(即,函數指針需要具有附加狀態)。

+0

我想即使是方法指針轉換回原始類型仍然必須仔細做,因爲指針可能不都是相同的大小。 – Cameron 2014-12-18 23:01:54