Чи може вказівник на базу вказувати на масив похідних об'єктів?


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 **Це вказує на масив Прямокутників. Тоді доступ повинен був мати форми [i] -> draw ();
RedX

2
@ Тоні удачі, інформуйте нас :)
Seth Carnegie,

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();
}

Якщо ви хочете мати різні види Shapes в масиві і використовувати їх поліморфно, вам потрібен масив покажчиків на 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();
}

Зауважте, що вам потрібно зробити додатковий крок ініціалізації прямокутника, оскільки ініціалізація масиву встановлює лише покажчики, а не самі об'єкти.


13

При індексації вказівника компілятор додасть відповідну суму, виходячи з розміру того, що знаходиться в масиві. Тому скажіть, що sizeof (Shape) = 4 (оскільки він не має змінних членів). Але sizeof (Прямокутник) = 12 (точні цифри, ймовірно, неправильні).

Отже, коли ви індексуєте, починаючи з кажуть ... 0x0 для першого елемента, тоді, коли ви намагаєтесь отримати доступ до 10-го елемента, ви намагаєтесь перейти до недійсної адреси або до місця, яке не є початком об'єкта.


1
Як адекватний c ++, згадка про SizeOf () допомогла мені зрозуміти, що таке @R. Мартиньо казав у своїй відповіді.
Мар'ян Венема
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.