Может ли указатель на базовую точку указывать на массив производных объектов?

Сегодня я отправился на собеседование и получил этот интересный вопрос.

Помимо утечки памяти и того факта, что виртуального 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();
}

Ответ 1

Вы не можете индексировать это. Вы выделили массив Rectangles и сохранили указатель на первый в shapes. Когда вы выполняете shapes[1], вы разыгрываете (shapes + 1). Это не даст вам указателя на следующий Rectangle, но указатель на то, что будет следующим Shape в предполагаемом массиве Shape. Конечно, это поведение undefined. В вашем случае вам повезет и вы получите крах.

Использование указателя на Rectangle делает работу индексации корректной.

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

Если вы хотите иметь разные типы Shape в массиве и использовать их полиморфно, вам нужен массив указателей на Shape.

Ответ 2

Как сказал Мартиньо Фернандес, индексирование неверно. Если бы вы захотели сохранить массив Shapes, вам нужно было бы сделать это с помощью массива 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, поскольку инициализация массива задает только указатели, а не сами объекты.

Ответ 3

При индексировании указателя компилятор добавит соответствующую сумму в зависимости от размера того, что находится внутри массива. Так скажите, что sizeof (Shape) = 4 (поскольку он не имеет переменных-членов). Но sizeof (Rectangle) = 12 (точные числа, скорее всего, неверны).

Итак, когда вы начинаете индексировать, начиная с say... 0x0 для первого элемента, тогда, когда вы пытаетесь получить доступ к 10-му элементу, вы пытаетесь перейти к недопустимому адресу или местоположению, а не к началу объекта.