Czy wskaźnik do bazy może wskazywać na tablicę obiektów pochodnych?

99

Poszedłem dziś na rozmowę kwalifikacyjną i zadano mi to interesujące pytanie.

Oprócz wycieku pamięci i faktu, że nie ma wirtualnego dtora, dlaczego ten kod się zawiesza?

#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();
}
Tony Lew
źródło
1
Masz na myśli oprócz brakującego średnika? (Byłby to jednak błąd w czasie kompilacji, a nie w czasie wykonywania)
Platinum Azure,
Czy na pewno wszystkie były wirtualne?
Yochai Timmer
8
Powinno być Shape **Wskazuje na tablicę prostokątów. Wtedy dostęp powinien mieć kształty [i] -> draw ();
RedX,
2
@Tony, powodzenia, informuj nas na bieżąco :)
Seth Carnegie,
2
@AndreyT: Kod jest teraz poprawny (i również pierwotnie był poprawny). To ->był błąd popełniony przez redaktora.
R. Martinho Fernandes,

Odpowiedzi:

150

Nie możesz tak indeksować. Przydzieliłeś tablicę Rectanglesi zapisałeś wskaźnik do pierwszego in shapes. Kiedy robisz shapes[1]wyłuskiwanie (shapes + 1). To nie da ci wskaźnika do następnego Rectangle, ale wskaźnik do tego, co będzie następne Shapew przypuszczalnej tablicy Shape. Oczywiście jest to niezdefiniowane zachowanie. W twoim przypadku masz szczęście i masz awarię.

Użycie wskaźnika, aby Rectangleindeksowanie działało poprawnie.

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

Jeśli chcesz mieć Shapew tablicy różne rodzaje znaków i używać ich polimorficznie, potrzebujesz tablicy wskaźników do Shape.

R. Martinho Fernandes
źródło
37

Jak powiedział Martinho Fernandes, indeksowanie jest nieprawidłowe. Jeśli zamiast tego chcesz przechowywać tablicę kształtów, musisz to zrobić za pomocą tablicy Shape *, na przykład:

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

Zwróć uwagę, że musisz wykonać dodatkowy krok inicjalizacji Rectangle, ponieważ inicjalizacja tablicy ustawia tylko wskaźniki, a nie same obiekty.

Patrick Costello
źródło
13

Podczas indeksowania wskaźnika kompilator doda odpowiednią ilość w oparciu o rozmiar tego, co znajduje się w tablicy. Powiedzmy, że sizeof (Shape) = 4 (ponieważ nie ma zmiennych składowych). Ale sizeof (Rectangle) = 12 (dokładne liczby są prawdopodobnie błędne).

Więc kiedy indeksujesz zaczynając od powiedzmy ... 0 x 0 dla pierwszego elementu, wtedy kiedy próbujesz uzyskać dostęp do dziesiątego elementu, próbujesz udać się pod nieprawidłowy adres lub lokalizację, która nie jest początkiem obiektu.

Jonathan Sternberg
źródło
1
Jako osoba nie znająca się na C ++ wspomnienie o SizeOf () pomogło mi zrozumieć, co @R. Martinho mówił w swojej odpowiedzi.
Marjan Venema