c++ 指向基类的指针可以指向派生对象的数组吗?

utugiqy6  于 2023-06-07  发布在  其他
关注(0)|答案(4)|浏览(182)

我今天去参加一个工作面试,被问了一个有趣的问题。
除了内存泄漏和没有虚拟dtor之外,为什么这段代码会崩溃?

#include <iostream>

//besides the obvious mem leak, why does this code crash?

class Shape
{
public:
    virtual void draw() const = 0;
};

class Circle : public Shape
{
public:
    virtual void draw() const { }

    int radius;
};

class Rectangle : public Shape
{
public:
    virtual void draw() const { }

    int height;
    int width;
};

int main()
{
    Shape * shapes = new Rectangle[10];
    for (int i = 0; i < 10; ++i)
        shapes[i].draw();
}
jaxagkaj

jaxagkaj1#

你不能这样索引。您已经分配了一个Rectangles数组,并在shapes中存储了指向第一个数组的指针。当你执行shapes[1]时,你是在解引用(shapes + 1)。这不会给予你一个指向下一个Rectangle的指针,而是一个指向Shape数组中下一个Shape的指针。当然,这是未定义的行为。就你而言,你很幸运,撞到了车。
使用指向Rectangle的指针可以使索引正确工作。

int main()
{
   Rectangle * shapes = new Rectangle[10];
   for (int i = 0; i < 10; ++i) shapes[i].draw();
}

如果你想在数组中有不同种类的Shape s并多态地使用它们,你需要一个指向Shape的 * 指针 * 数组。

oaxa6hgo

oaxa6hgo2#

正如Martinho Fernandes所说,索引是错误的。如果你想存储一个Shape数组,你必须使用一个Shape * 的数组,如下所示:

int main()
{
   Shape ** shapes = new Shape*[10];
   for (int i = 0; i < 10; ++i) shapes[i] = new Rectangle;
   for (int i = 0; i < 10; ++i) shapes[i]->draw();
}

请注意,您必须执行初始化Rectangle的额外步骤,因为初始化数组只设置指针,而不是对象本身。

omqzjyyz

omqzjyyz3#

当索引一个指针时,编译器将根据数组中的大小添加适当的数量。假设sizeof(Shape)= 4(因为它没有成员变量)。但是sizeof(Rectangle)= 12(确切的数字可能是错误的)。
所以当你索引开始于... 0x0的第一个元素,那么当你试图访问第10个元素,你试图去一个无效的地址或一个位置,这不是开始的对象。

soat7uwm

soat7uwm4#

CB Bailey的This answer从语言规范的Angular 来看更准确。它更清楚地解释了为什么访问shapes[i]是未定义的行为。这个答案在下面引用,指针变量改为shapesShape作为基类,Rectangle作为派生类(而不是那个问题中的pBaseDerived)。
如果你看表达式shapes[1]shapes是一个Shape*(Shape>一个完全定义的类型),1是一个int,所以根据ISO/IEC 14882:2003 5.2.1 [expr.sub],这个表达式是有效的,与*((shapes)+(1))相同。
因此,访问shapes[i]不是病态的。然而,如下所述,它是未定义的。
从5.7 [expr.add] / 5开始,当一个整数加到一个指针上时,只有当指针指向数组对象的一个元素,并且指针运算的结果也指向该数组对象的一个元素或超过数组末尾的一个元素时,结果才是明确定义的。但是,shapes并不指向数组对象的元素,而是指向派生对象的基类子对象。数组成员是Rectangle对象,而不是Shape子对象。
注意,在5.7 / 4中,出于加法运算符的目的,Shape子对象可以被视为大小为1的数组,因此从技术上讲,您可以形成地址shapes + 1,但作为“最后一个元素”的指针,它并不指向Shape对象,并且尝试读取或写入它将导致未定义的行为。
这些段落很好地说明了如何处理表达式shapes[i]

相关问题