2014-12-18 22 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