当我们进行下行广播时,内部会发生什么
What happens internally when we do downcasting?
我试着理解向下投射。。。以下是我尝试过的。。。
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;
}
在这里,我通过转换将基类指针分配给派生类来减少向下转换。我所理解的是…
Circle* ptrCircle1 --> +------+ new Shape()
|draw()|
+------+
基类没有关于派生调用中存在的display()
方法的信息。我本以为会崩溃,但它确实将输出打印为
Shape: Draw Method
Circle: Only CIRCLE has this
有人能解释一下内部发生了什么吗。
谢谢。。。
在这种情况下,由于继承关系,C样式转换等效于static_cast
。与大多数类型转换一样(dynamic_cast
除外,在CCD_3中注入了一些检查(,当你告诉它对象实际上是Circle
时,编译器会信任你并认为它是。在这种情况下,行为是未定义,因为对象是而不是Circle
,所以你在欺骗编译器,所有的赌注都是无效的。
这里真正发生的是,编译器计算这个组合从基到派生类型是否有偏移,并相应地调整指针。此时,您将获得一个指向已调整地址的派生类型的指针,并且类型安全性不在窗口中。通过该指针进行的任何访问都会假设该位置的内存是你告诉它的,并将其解释为这样,这是未定义的行为,因为你正在读取内存,就好像它不是一种类型一样。
何时调整指针
struct base1 { int x; };
struct base2 { int y; };
struct derived : base1, base2 {};
base2 *p = new derived;
derived
、base1
和base1::x
的地址与base2
和base2::y
的地址相同,但不同。如果从derived
强制转换为base2
,编译器会在转换中调整指针(将sizeof(base1)
添加到地址(,当从base2
强制转换为CCD _15时,编译器会朝相反的方向调整。
你为什么会得到你得到的结果
形状:绘制方法
Circle:只有Circle有这个
这与编译器如何实现动态调度有关。对于每个至少有一个虚拟函数的类型,编译器将生成一个(或多个(虚拟表。虚拟表包含指向类型中每个函数的最终重写器的指针。每个对象都有一个指向完整类型的虚拟表的指针。调用虚拟函数需要编译器在表中进行查找并跟随指针。
在这种情况下,对象实际上是Shape
,vptr将引用Shape
的虚拟表。当您从Shape
强制转换为Derived
时,您会告诉编译器这是一个Circle
(即使不是(。当您调用draw()
时,编译器会跟随vptr(在这种情况下,Shape
子对象和Circle
子对象的vptr恰好位于距离对象开头相同的偏移量(在大多数ABI中为0(。编译器注入的调用遵循Shape
vptr(强制转换不会更改内存的任何内容,vptr仍然是Shape
的vptr(,然后命中Shape::draw
。
在display()
的情况下,调用不是通过vptr动态调度的,因为它不是虚拟函数。这意味着编译器将直接调用Circle::draw()
,传递您作为this
指针的地址。您可以通过禁用动态调度来模拟虚拟功能:
ptrCircle1->Circle::draw();
请记住,这只是对编译器细节的解释,这些细节脱离了C++标准,根据标准,这只是未定义的行为,编译器所做的一切都很好。不同的编译器可以做一些不同的事情(尽管我在这里看到的所有ABI基本上都是一样的(。
如果你真的对这些东西是如何工作的细节感兴趣,你可以看看Lippman的《C++对象模型内部》。这是一本古老的书,但它解决了编译器必须解决的问题以及编译器使用的一些解决方案。
由于display()
不是虚拟的,因此在大多数c++实现中调用它不使用指针值。因此,您正在通过其静态地址调用display()
。由于display((不使用this
,所以它可以工作。
然而,正如评论所指出的,这仍然是未定义的行为。另一个编译器可能会导致崩溃。
您也可以从nullptr
指针调用display()
,这将在大多数实现中提供相同的结果。但仍有未定义的行为。
- 正确的方法是什么?调用指针到指针到指针内部的函数?
- 这个阶乘程序内部发生了什么?
- 什么是非时态流式处理负载内部 (_mm256_stream_load_si256) 的浮点 (__m256d) 版本?
- C 是什么是在结构内部洗牌的最有效方法
- 当指针作为指向另一个功能内部指针的指针传递时会发生什么
- C++内部联动有什么意义
- 有什么区别:在命名空间 yyy 外部和内部使用 namespae xxx?
- C 如果实际上是继承了类,则内部发生了什么
- 内部循环的上限是什么
- 什么是内部::状态::输入,并且在中间定义了什么
- 在内部和外部与调试器之间运行有什么区别
- 当您在设备内部调用cudaMalloc时,实际会发生什么
- 椭圆内部是什么颜色?
- 内部编译器错误消息意味着什么,我能做些什么
- 如果我只在发布配置中遇到内部编译器错误,我可以看看什么来解决这个问题
- 仅用于内部目的的类的所有变量/成员的技术术语是什么
- 有什么方法可以在工会内部获得受保护的声明吗
- 什么..表示函数内部参数(const char*值,..)
- 如何谷歌奇怪的字符,例如"~"内部问题,例如在构造函数之前的 c++ 中这意味着什么?
- shared_ptr控制块内部的虚拟功能是什么?