为什么原始的多态类型数组是危险的

时间:2015-11-06 18:28:22

标签: c++ arrays polymorphism

为了更好地理解我的问题,我指的是“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();

假设我是对的,派生对象/它的布局的大小如何打破它的调用。

1 个答案:

答案 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();

也就是说,将pCircle s数组的开头)增加sizeof(Shape)个字节,然后取消引用它以调用draw()。但是这个索引是错误的,因为它应该是sizeof(Circle)个字节。而C,它给我们提供了数组类型,并没有给我们任何直接处理它的工具,因为它没有预见到C ++。

请注意,除了使上面的编译时错误(我认为比运行时未定义的行为更好)之外,C ++ 11 std::array<>将无济于事。用C风格的数组)。好老std::vector<>是一样的。您可以存储指针数组,在这种情况下,它们通常应该是std::shared_ptr之类的智能指针。这是有效的,因为指向任何类型的指针(除了指向成员函数的指针!)都是相同的大小,因此通过它们的数组索引工作正常。