Un pointeur vers la base peut-il pointer vers un tableau d'objets dérivés?

99

Je suis allé à un entretien d'embauche aujourd'hui et j'ai reçu cette question intéressante.

Outre la fuite de mémoire et le fait qu'il n'y ait pas de dtor virtuel, pourquoi ce code plante-t-il?

#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 le lion
la source
1
Outre le point-virgule manquant, vous voulez dire? (Ce serait une erreur de compilation, cependant, pas d'exécution)
Platinum Azure
Etes-vous sûr qu'ils étaient tous virtuels?
Yochai Timmer
8
Cela devrait être Shape **Il pointe vers un tableau de rectangles. Ensuite, l'accès aurait dû être des formes [i] -> draw ();
RedX
2
@Tony bonne chance alors, tenez-nous au courant :)
Seth Carnegie
2
@AndreyT: Le code est maintenant correct (et était également correct à l'origine). Le ->était une erreur commise par un éditeur.
R. Martinho Fernandes

Réponses:

150

Vous ne pouvez pas indexer comme ça. Vous avez alloué un tableau de Rectangleset stocké un pointeur vers le premier dans shapes. Lorsque vous faites shapes[1]vous déréférencer (shapes + 1). Cela ne vous donnera pas un pointeur vers le suivant Rectangle, mais un pointeur vers ce qui serait le suivant Shapedans un tableau présumé de Shape. Bien sûr, ce comportement n'est pas défini. Dans votre cas, vous avez de la chance et vous avez un accident.

L'utilisation d'un pointeur sur Rectanglefait fonctionner correctement l'indexation.

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

Si vous voulez avoir différents types de Shapes dans le tableau et les utiliser de manière polymorphe, vous avez besoin d'un tableau de pointeurs vers Shape.

R. Martinho Fernandes
la source
37

Comme l'a dit Martinho Fernandes, l'indexation est erronée. Si vous souhaitez à la place stocker un tableau de formes, vous devez le faire en utilisant un tableau de formes *, comme ceci:

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

Notez que vous devez faire une étape supplémentaire d'initialisation du Rectangle, car l'initialisation du tableau ne configure que les pointeurs, et non les objets eux-mêmes.

Patrick Costello
la source
13

Lors de l'indexation d'un pointeur, le compilateur ajoutera le montant approprié en fonction de la taille de ce qui se trouve à l'intérieur du tableau. Disons donc que sizeof (Shape) = 4 (car il n'a pas de variables membres). Mais sizeof (Rectangle) = 12 (les nombres exacts sont probablement faux).

Ainsi, lorsque vous indexez à partir de disons ... 0x0 pour le premier élément, lorsque vous essayez d'accéder au 10ème élément, vous essayez d'accéder à une adresse invalide ou à un emplacement qui n'est pas le début de l'objet.

Jonathan Sternberg
la source
1
En tant que non adepte du C ++, mentionner SizeOf () m'a aidé à comprendre ce que @R. Martinho disait dans sa réponse.
Marjan Venema