Kann ein Zeiger auf die Basis auf ein Array abgeleiteter Objekte zeigen?

99

Ich ging heute zu einem Vorstellungsgespräch und bekam diese interessante Frage.

Warum stürzt dieser Code ab, abgesehen von dem Speicherverlust und der Tatsache, dass es keinen virtuellen dtor gibt?

#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 der Löwe
quelle
1
Neben dem fehlenden Semikolon meinst du? (Das wäre jedoch ein Fehler zur Kompilierungszeit, keine Laufzeit)
Platinum Azure
Sind Sie sicher, dass sie alle virtuell waren?
Yochai Timmer
8
Es sollte sein, dass Shape **es auf eine Reihe von Rechtecken zeigt. Dann sollte der Zugriff Formen [i] -> draw () gewesen sein;
RedX
2
@ Tony viel Glück dann, halten Sie uns auf dem Laufenden :)
Seth Carnegie
2
@AndreyT: Der Code ist jetzt korrekt (und war ursprünglich auch korrekt). Das ->war ein Fehler eines Redakteurs.
R. Martinho Fernandes

Antworten:

150

Sie können so nicht indizieren. Sie haben ein Array von zugewiesen Rectanglesund einen Zeiger auf das erste in gespeichert shapes. Wenn Sie dies tun shapes[1], dereferenzieren Sie (shapes + 1). Dies gibt Ihnen keinen Zeiger auf den nächsten Rectangle, sondern einen Zeiger auf den nächsten Shapein einem vermuteten Array von Shape. Dies ist natürlich undefiniertes Verhalten. In Ihrem Fall haben Sie Glück und bekommen einen Absturz.

Wenn Sie einen Zeiger verwenden, Rectanglefunktioniert die Indizierung ordnungsgemäß.

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

Wenn Sie verschiedene Arten von Shapes im Array haben und diese polymorph verwenden möchten, benötigen Sie ein Array von Zeigern auf Shape.

R. Martinho Fernandes
quelle
37

Wie Martinho Fernandes sagte, ist die Indizierung falsch. Wenn Sie stattdessen ein Array von Shapes speichern möchten, müssen Sie dazu ein Array von Shape * verwenden, wie folgt:

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

Beachten Sie, dass Sie einen zusätzlichen Schritt zum Initialisieren des Rechtecks ​​ausführen müssen, da beim Initialisieren des Arrays nur die Zeiger und nicht die Objekte selbst eingerichtet werden.

Patrick Costello
quelle
13

Beim Indizieren eines Zeigers fügt der Compiler den entsprechenden Betrag hinzu, der auf der Größe der Elemente im Array basiert. Nehmen wir also an, dass sizeof (Shape) = 4 ist (da es keine Mitgliedsvariablen gibt). Aber sizeof (Rechteck) = 12 (genaue Zahlen sind wahrscheinlich falsch).

Wenn Sie also ab ... 0x0 für das erste Element indizieren, versuchen Sie beim Zugriff auf das 10. Element, zu einer ungültigen Adresse oder einem Ort zu wechseln, der nicht der Anfang des Objekts ist.

Jonathan Sternberg
quelle
1
Als Nicht-C ++ - Adept hat mir die Erwähnung von SizeOf () geholfen zu verstehen, was @R ist. Martinho sagte in seiner Antwort.
Marjan Venema