为了更好地理解我的问题,我指的是“The C ++ Programming Language 4th edition”一章第27章2.1节中讨论的主题。
作者正在谈论多态类型和内置数组的危险性。 他给出了以下例子:
void maul(Shape∗ p, int n) // Danger!
{
for (int i=0; i!=n; ++i)
p[i].draw(); //looks innocent; it is not
}
void user()
{
Circle image[10]; // an image is composed of 10 Circles
// ...
maul(image,10); // ‘‘maul’’ 10 Circles
// ...
}
我们被告知Shape是一个大小为4的抽象大小,Circle继承了Shape并添加了额外的2个成员,center和radius,它们增加了类型大小,因此sizeof(Circle)>sizeof(Shape)
。
现在作者解释说,例如以下视图:
user() view: image[0] image[1] image[2] image[3]<br/>
maul() view: p[0] p[1] p[2] p[3]
p[1].draw()
的调用(强调p [ 1 ],对于p [0],它将调用正确的函数)将失败,因为没有虚拟函数指针在预期的位置。
现在我知道虚函数表是如何工作的,但是我不明白类型或其布局的大小如何影响虚函数调用?当编译器看到对虚函数的调用时,它不会用以下类似的东西替换它:
p[1]._vfptr->draw_impl();
假设我是对的,派生对象/它的布局的大小如何打破它的调用。
答案 0 :(得分:0)
当i
为0时,这有效:
p[i].draw(); // Shape* p
p[0]
与*(p+0)
相同,只是*p
,并且通过基类指针调用函数正是虚拟函数旨在支持的功能。
但是当i
为1时呢?现在你有p[1]
*(p+1)
。什么是p+1
,嗯,它是p
之后“下一个”对象的地址。但是数组索引是来自C的概念,其中没有虚拟表或派生类,因此通过添加{em>编译时 sizeof(*p)
sizeof(Shape)
来完成索引。你得到的结果如下:
((Shape*)((char*)p + sizeof(Shape)))->draw();
也就是说,将p
(Circle
s数组的开头)增加sizeof(Shape)
个字节,然后取消引用它以调用draw()
。但是这个索引是错误的,因为它应该是sizeof(Circle)
个字节。而C,它给我们提供了数组类型,并没有给我们任何直接处理它的工具,因为它没有预见到C ++。
请注意,除了使上面的编译时错误(我认为比运行时未定义的行为更好)之外,C ++ 11 std::array<>
将无济于事。用C风格的数组)。好老std::vector<>
是一样的。您可以存储指针数组,在这种情况下,它们通常应该是std::shared_ptr
之类的智能指针。这是有效的,因为指向任何类型的指针(除了指向成员函数的指针!)都是相同的大小,因此通过它们的数组索引工作正常。