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

99

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

Помимо утечки памяти и отсутствия виртуального 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
Вы имеете в виду, кроме пропущенной точки с запятой? (Это будет ошибка времени компиляции, а не времени выполнения)
Platinum Azure
Вы уверены, что все они были виртуальными?
Йохай Тиммер
8
Должно быть, Shape **он указывает на массив прямоугольников. Тогда доступ должен был быть shape [i] -> draw ();
RedX
2
@ Тони, удачи, держи нас в курсе :)
Сет Карнеги,
2
@AndreyT: Сейчас код правильный (и тоже был правильным изначально). ->Была ошибка , сделанная редактором.
Р. Мартиньо Фернандес

Ответы:

150

Вы не можете так индексировать. Вы выделили массив 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.

Р. Мартиньо Фернандес
источник
37

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

Патрик Костелло
источник
13

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

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

Джонатан Штернберг
источник
1
Как не адепт C ++, упоминание SizeOf () помогло мне понять, что такое @R. - сказал Мартиньо в своем ответе.
Марьян Венема