Heritância múltipla: resultado inesperado após lançado de vazio * para a 2ª classe base
-
24-09-2019 - |
Pergunta
Meu programa precisa fazer uso do void* para transportar dados ou objetos em situação de invocação dinâmica, para que possa fazer referência a dados de tipos arbitrários, até tipos primitivos. No entanto, descobri recentemente que o processo de queda neles vazios* em caso de classes com várias classes base falha e até trava meu programa depois de invocar métodos nesses ponteiros escalados, mesmo que os endereços de memória pareçam estar corretos. O acidente acontece durante o acesso a "vtable".
Então, eu criei um pequeno caso de teste, o ambiente é o GCC 4.2 no Mac OS X:
class Shape {
public:
virtual int w() = 0;
virtual int h() = 0;
};
class Square : public Shape {
public:
int l;
int w() {return l;}
int h() {return l;}
};
class Decorated {
public:
int padding;
int w() {return 2*padding;}
int h() {return 2*padding;}
};
class DecoratedSquare : public Square, public Decorated {
public:
int w() {return Square::w() + Decorated::w();}
int h() {return Square::h() + Decorated::h();}
};
#include <iostream>
template <class T> T shape_cast(void *vp) {
// return dynamic_cast<T>(vp); // not possible, no pointer to class type
// return static_cast<T>(vp);
// return T(vp);
// return (T)vp;
return reinterpret_cast<T>(vp);
}
int main(int argc, char *argv[]) {
DecoratedSquare *ds = new DecoratedSquare;
ds->l = 20;
ds->padding = 5;
void *dsvp = ds;
std::cout << "Decorated (direct)" << ds->w() << "," << ds->h() << std::endl;
std::cout << "Shape " << shape_cast<Shape*>(dsvp)->w() << "," << shape_cast<Shape*>(dsvp)->h() << std::endl;
std::cout << "Square " << shape_cast<Square*>(dsvp)->w() << "," << shape_cast<Square*>(dsvp)->h() << std::endl;
std::cout << "Decorated (per void*) " << shape_cast<Decorated*>(dsvp)->w() << "," << shape_cast<Decorated*>(dsvp)->h() << std::endl;
std::cout << "DecoratedSquare " << shape_cast<DecoratedSquare*>(dsvp)->w() << "," << shape_cast<DecoratedSquare*>(dsvp)->h() << std::endl;
}
produz a seguinte saída:
Decorated (direct)30,30
Shape 30,30
Square 30,30
Decorated (per void*) 73952,73952
DecoratedSquare 30,30
Como você pode ver, o resultado "decorado (por vazio*)" está completamente errado. Também deve ser 30,30, como na primeira linha.
Qualquer que seja o método de elenco que eu use em shape_cast (), sempre obterei os mesmos resultados inesperados para a parte decorada. Algo está completamente errado com esses vazios *.
Pela minha compreensão do C ++, isso deve estar realmente funcionando. Existe alguma chance de fazer isso funcionar com o vazio*? Isso pode ser um bug no GCC?
Obrigado
Solução
Não é um bug do compilador - é o que reinterpret_cast
faz. o DecoratedSquare
O objeto será apresentado na memória algo assim:
Square
Decorated
DecoratedSquare specific stuff
Convertendo um ponteiro para isso para void*
fornecerá o endereço do início desses dados, sem nenhum conhecimento de que tipo existe. reinterpret_cast<Decorated*>
levará esse endereço e interpretará o que estiver lá como um Decorated
- mas o conteúdo real da memória é o Square
. Isso está errado, então você obtém comportamento indefinido.
Você deve obter os resultados corretos se você reinterpret_cast
para o tipo dinâmico correto (isto é DecoratedSquare
), em seguida, converta -se para a classe base.
Outras dicas
Repita dez vezes - a única coisa que você pode fazer com segurança com um reinterpret_cast
ponteiro é reinterpret_cast
De volta ao mesmo tipo de ponteiro de onde veio. O mesmo se aplica com conversões para void*
: Você deve converter de volta ao tipo original.
Então, se você lançar um DecoratedSquare*
para void*
, você deve lançá -lo de volta para DecoratedSquare*
. Não Decorated*
, não Square*
, não Shape*
. Alguns deles podem funcionar em sua máquina, mas essa é uma combinação de boa sorte e comportamento específico da implementação. Geralmente, funciona com uma excitação única, porque não há motivo óbvio para implementar ponteiros de objeto de uma maneira que o impediria de funcionar, mas isso não é garantido e não pode funcionar em geral para obter herança múltipla.
Você diz que seu código acessa "tipos arbitrários, incluindo tipos primitivos" por meio de um vazio*. Não há nada de errado com isso - presumivelmente quem recebe os dados que sabe tratá -los como um DecoratedSquare*
e não como, digamos, int*
.
Se quem recebe apenas sabe tratá -lo como uma classe base, como Decorated*
, então quem quer que o converta para void*
deve static_cast
para a classe base primeiro, depois para void*
:
void *decorated_vp = static_cast<Decorated*>(ds);
Agora quando você lança decorated_vp
de volta a Decorated*
, você terá o resultado de static_cast<Decorated*>(ds)
, que é o que você precisa.
Um static_cast ou um dinâmico_cast na presença de herança múltipla pode alterar a representação do ponteiro, compensando -o para que ele designasse o endereço correto. Static_cast determina o deslocamento correto, considerando as informações de digitação estática. Dynamic_cast faz isso verificando o tipo dinâmico. Se você for lançar o vazio*, estará perdendo todas as informações de digitação estática e a possibilidade de obter informações de digitação dinâmica; portanto, o reinterpret_cast que você está usando está assumindo que o deslocamento é nulo, falhando algumas vezes.