Sta*_*kIT 2 c++ casting virtual-functions downcast
我试图了解向下...这是我尝试过的......
class Shape
{
public:
Shape() {}
virtual ~Shape() {}
virtual void draw(void) { cout << "Shape: Draw Method" << endl; }
};
class Circle : public Shape
{
public:
Circle(){}
~Circle(){}
void draw(void) { cout << "Circle: Draw Method" << endl; }
void display(void) { cout << "Circle: Only CIRCLE has this" << endl; }
};
int main(void)
{
Shape newShape;
Circle *ptrCircle1 = (Circle *)&newShape;
ptrCircle1->draw();
ptrCircle1->display();
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
在这里,我通过将基类指针转换为派生类来进行向下转换.我的理解是......
Circle* ptrCircle1 --> +------+ new Shape()
|draw()|
+------+
Run Code Online (Sandbox Code Playgroud)
基类没有关于display()派生调用中的方法的信息.我原本期待一次崩溃,但确实将输出打印为
Shape: Draw Method
Circle: Only CIRCLE has this
Run Code Online (Sandbox Code Playgroud)
有人可以解释内部发生的事情.
谢谢...
在这种情况下,由于继承关系,C风格的强制转换相当于static_cast.与大多数强制转换一样(除了dynamic_cast注入某些检查的地方),当你告诉它对象确实是a时Circle,编译器会信任你并假设它是.在这种情况下,行为是未定义的,因为对象不是 a Circle,您对编译器撒谎并且所有赌注都关闭.
这里真正发生的是编译器确定该组合是否存在从基类到派生类型的偏移量并相应地调整指针.此时,您将获得一个指向已调整地址的派生类型的指针,并且窗口中的类型安全性已关闭.通过该指针进行的任何访问都将假定该位置的内存是您告诉它的内容,并将其解释为未定义的行为,因为您正在读取内存,就好像它是一种类型的内存一样.
指针何时调整?
struct base1 { int x; };
struct base2 { int y; };
struct derived : base1, base2 {};
base2 *p = new derived;
Run Code Online (Sandbox Code Playgroud)
的地址derived,base1并且base1::x是一样的,但是从地址不同base2和base2::y.如果你从编译器转换derived到base2编译器会调整转换中的指针(添加sizeof(base1)到地址),当从base2to 转换时derived,编译器会调整相反的方向.
你为什么得到你得到的结果?
形状:绘制方法
圈子:只有CIRCLE才有这个
这与编译器如何实现动态调度有关.对于具有至少一个虚函数的每个类型,编译器将生成一个(或多个)虚拟表.虚拟表包含指向类型中每个函数的最终覆盖的指针.每个对象都包含指向完整类型的虚拟表的指针.调用虚函数涉及编译器在表中执行查找并跟随指针.
在这种情况下,对象实际上是a Shape,vptr将引用虚拟表 Shape.当你转换Shape为Derived你告诉编译器这是一个Circle(即使它不是).当你调用draw()编译器遵循的vptr(在这种情况下,对于vptr的Shape子对象和Circle子对象恰好是在同一个从对象的开始最ABI的偏移量(0),由编译器注入的通话遵循Shape的vptr(剧组也不会改变内存中的任何内容,即vptr的仍然是的Shape)和命中Shape::draw.
如果display()调用不通过vptr动态调度,因为它不是虚函数.这意味着编译器将注入一个直接调用来Circle::draw()传递您拥有的地址作为this指针.您可以通过禁用动态分派来模拟虚拟功能:
ptrCircle1->Circle::draw();
Run Code Online (Sandbox Code Playgroud)
请记住,这只是对逃避C++标准的编译器细节的解释,标准这只是未定义的行为,无论编译器做什么都没关系.一个不同的编译器可以做一些不同的事情(虽然我见过的所有ABI在这里做的基本相同).
如果您真的对这些内容的工作原理感兴趣,可以查看Lippman 的C++内部对象模型.这是一本不太老的书,但它解决了编译器必须解决的问题以及编译器使用的一些解决方案.