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

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top