假设一个层次结构具有两个不相关的多态类PCH
和GME
,一个子类PCH_GME : public GME, public PCH
和一个类型为gme_pch
的对象PCH_GME*
。
为什么gme_pch
的以下强制转换序列会“中断”强制转换为对象的原始类型GME_PCH*
:
GME_PCH *gme_pch = new GME_PCH();
GME *gme = gme_pch;
PCH *pch = (PCH*)gme;
GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);
// same_as_gme_pch is NULL
以下代码不会破坏类型转换:
GME_PCH *gme_pch = new GME_PCH();
PCH *pch = gme_pch;
GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);
// address of same_as_gme_pch == gme_pch
问题:不是每个指针都始终指向同一个对象,然后将最终转换回原始类型的结果不应该总是相同吗?
编辑:根据答案,我添加了gme_pch
和pch
地址的输出。它表明,在工作的变体中,这两个指针的关系与不起作用的变体是不同的(即,取决于写的是GME_PCE : public GME, public PCH
还是GME_PCE : public PCH, public GME
,gme_pch
等于{有效变体中的{1}}和非有效变体中的pch
不相等,反之亦然。
仅仅是为了使尝试变得更容易,请参见以下代码,演示上述转换序列的变体;有些工作,有些却没有:
gme_pch
输出:
class PCH { // PrecachingHint
public:
virtual std::string getHint() const = 0;
};
class GME { // GenericModelElement
public:
virtual std::string getKey() const = 0;
};
class GME_PCH : public GME, public PCH {
public:
virtual std::string getHint() const { return "some hint"; }
virtual std::string getKey() const { return "some key"; }
};
void castThatWorks() {
GME_PCH *gme_pch = new GME_PCH();
PCH *pch = gme_pch;
GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);
std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}
void castThatWorks2() {
GME_PCH *gme_pch = new GME_PCH();
GME *gme = gme_pch;
PCH *pch = dynamic_cast<PCH*>(gme);
GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);
std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}
void castThatDoesntWork() {
GME_PCH *gme_pch = new GME_PCH();
GME *gme = gme_pch; // note: void* gme = gme_pch breaks the subsequent dynamic cast, too.
PCH *pch = (PCH*)gme;
GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);
std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}
void castThatDoesntWork2() {
GME_PCH *gme_pch = new GME_PCH();
GME *gme = gme_pch;
PCH *pch = reinterpret_cast<PCH*>(gme);
GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);
std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}
void castThatDoesntWork3() {
GME_PCH *gme_pch = new GME_PCH();
GME *gme = gme_pch;
PCH *pch = static_cast<PCH*>(static_cast<void*>(gme));
GME_PCH *same_as_gme_pch = dynamic_cast<GME_PCH*>(pch);
std::cout << ((same_as_gme_pch == nullptr) ? "cast did not work." : "cast worked.")<< "gmepch:" << gme_pch << "; pch:" << pch << std::endl;
}
int main() {
castThatWorks();
castThatWorks2();
castThatDoesntWork();
castThatDoesntWork2();
castThatDoesntWork3();
}
答案 0 :(得分:4)
GME_PCH *gme_pch = new GME_PCH();
GME *gme = gme_pch;
PCH *pch = static_cast<PCH*>(static_cast<void*>(gme));
当GME_PCH
和GME
都被多重继承时,是错误的。不要使用它。
通过尝试以下操作,您会发现为什么这是错误的:
PCH
您会注意到GME_PCH *gme_pch = new GME_PCH();
GME *gme = gme_pch;
PCH *pch1 = gme_pch; // Implicit conversion. Does the right offsetting of pointer
PCH *pch2 = static_cast<PCH*>(static_cast<void*>(gme)); // Wrong.
std::cout << "pch1: " << pch1 << ", pch2: " << pch2 << std::endl;
和pch1
是不同的。 pch2
是有效值,而pch1
不是有效值。
答案 1 :(得分:2)
当使用隐式转换,GME_PCH *
或PCH *
将static_cast
转换为dynamic_cast
时,结果指向对象的PCH
子对象。 GME_PCH
个对象。
但是,当您使用GME_PCH *
将PCH *
转换为reinterpret_cast
时,结果将使地址保持不变:它指向GME_PCH
对象的内存位置,通常是GME
子对象所在的位置(编译器通常将多态对象以内存中的第一个基类放在第一位)。
您的非工作尝试均等效于reinterpret_cast<PCH *>(gme_pch)
。它们失败是因为您最终得到的类型为PCH *
的指针没有指向PCH
对象。
如果这是有效的,则C样式强制转换的行为类似于static_cast
,否则类似于reinterpret_cast
。
代码(PCH *)gme_pch
是static_cast<PCH *>(gme_pch)
,但是代码(PCH *)gme
是reinterpret_cast<PCH *>(gme)
。
要从PCH
进入GME
,您需要使用dynamic_cast
,它可以测试GME
是否实际上是{{1}的一部分} 或不。如果不是,则强制类型转换将产生空指针。
答案 2 :(得分:2)
PCH *pch = (PCH*)gme;
使用C样式转换停止。这行代码没有任何合理的意义;它将gme
的位重新解释为指向一件事的指针,并说“如果这些位引用了另一种类型该怎么办”。
但是GME和PCH子对象的地址不相同,因此获得的指针是垃圾。然后其他一切都会失败。
该行也可以写为PCH *pch = reinterpret_cast<PCH*>(gme);
C样式强制转换在任何情况下都是合理或危险的。
此PCH *pch = static_cast<PCH*>(static_cast<void*>(gme));
违反了另一条规则;投射到void*
时,应始终将还原为与您投射时相同的类型。
在某些情况下,重新解释强制转换(或通过void进行的错误跳转)会起作用;但是它们很脆弱,并且在标准中包含了比较深奥的文字。
总是总是将void ptr返回其确切的原始类型,并且永远不要重新解释对其他类型的强制转换或C样式强制转换指针。
答案 3 :(得分:1)
使用继承时,即使没有多重继承,也需要将指针值从指向派生类的指针调整为可能与基类不同的指针。例如,如果您在派生类中添加虚拟方法,则指向基类的指针通常会根据指向vtable的指针大小进行调整:
struct foo
{
int whatever;
};
struct bar: public foo
{
virtual void what();
};
栏布局:
pvtable←栏*将指向此处
int←foo *将指向此处
此外,在虚拟继承的情况下,将不可能找出正确的指针调整,因此基类指针的实际值显式存储在每个派生的类实例中。