Um ponteiro para a base pode apontar para uma matriz de objetos derivados?

99

Fui a uma entrevista de emprego hoje e recebi uma pergunta interessante.

Além do vazamento de memória e do fato de não haver dtor virtual, por que esse código trava?

#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 o Leão
fonte
1
Além do ponto-e-vírgula ausente, você quer dizer? (Isso seria um erro em tempo de compilação, mas não em tempo de execução)
Platinum Azure
Tem certeza de que eram todos virtuais?
Yochai Timmer
8
Deveria ser. Shape **Está apontando para uma série de retângulos. Então o acesso deveria ter sido shapes [i] -> draw ();
RedX de
2
@Tony boa sorte então, mantenha-nos informados :)
Seth Carnegie
2
@AndreyT: O código está correto agora (e também estava correto originalmente). Isso ->foi um erro cometido por um editor.
R. Martinho Fernandes

Respostas:

150

Você não pode indexar assim. Você alocou um array de Rectanglese armazenou um ponteiro para o primeiro em shapes. Ao fazer isso, shapes[1]você está desreferenciando (shapes + 1). Isso não fornecerá um ponteiro para o próximo Rectangle, mas um ponteiro para o que seria o próximo Shapeem uma matriz presumida de Shape. Claro, esse é um comportamento indefinido. No seu caso, você está tendo sorte e travando.

Usar um ponteiro para Rectanglefaz com que a indexação funcione corretamente.

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

Se você deseja ter diferentes tipos de Shapes no array e usá-los polimorficamente, você precisa de um array de ponteiros para Shape.

R. Martinho Fernandes
fonte
37

Como disse Martinho Fernandes, a indexação está errada. Se, em vez disso, você quiser armazenar uma matriz de Formas, deverá fazer isso usando uma matriz de Formas *, assim:

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

Observe que você precisa realizar uma etapa extra para inicializar o retângulo, já que inicializar o array apenas configura os ponteiros, e não os próprios objetos.

Patrick Costello
fonte
13

Ao indexar um ponteiro, o compilador adicionará a quantidade apropriada com base no tamanho do que está localizado dentro do array. Portanto, diga que sizeof (Shape) = 4 (já que não possui variáveis ​​de membro). Mas sizeof (Rectangle) = 12 (os números exatos provavelmente estão errados).

Então, quando você indexa começando em digamos ... 0 x 0 para o primeiro elemento, quando você tenta acessar o décimo elemento, você está tentando ir para um endereço inválido ou um local que não é o início do objeto.

Jonathan Sternberg
fonte
1
Como um não adepto do c ++, mencionar SizeOf () me ajudou a entender o que é @R. Martinho dizia na sua resposta.
Marjan Venema