Pergunta

Para um sistema, preciso converter um ponteiro para um longo e depois o longo de volta ao tipo de ponteiro. Como você pode imaginar, isso é muito inseguro. O que eu queria fazer é usar o Dynamic_cast para fazer a conversão, por isso, se eu misturá -los, receberei um ponteiro nulo. Esta página diz http://publib.boulder.ibm.com/infocenter/lnxpcomp/v7v91/index.jsp?topic=/com.ibm.vacpp7l.doc/language/ref/clrc05keyword_dynamic_cast.htm

O operador dinâmico_cast executa conversões de tipo no tempo de execução. O operador dinâmico_cast garante a conversão de um ponteiro em uma classe base em um ponteiro para uma classe derivada ou a conversão de um LValue referente a uma classe base a uma referência a uma classe derivada. Um programa pode, assim, usar uma hierarquia de classe com segurança. Esse operador e o operador do TypeID fornecem suporte ao tipo de tempo de execução (RTTI) no C ++.

E eu gostaria de receber um erro se for nulo, então escrevi meu próprio elenco dinâmico

template<class T, class T2> T mydynamic_cast(T2 p)
{
    assert(dynamic_cast<T>(p));
    return reinterpret_cast<T>(p);
}

Com o MSVC, recebo o erro "Erro C2681: 'Long': Tipo de expressão inválido para dinâmico_cast". Acontece que isso funcionará apenas com classes que têm funções virtuais ... WTF! Sei que o ponto de um elenco dinâmico era para o problema de herança de elenco para cima/para baixo, mas também pensei que era para resolver o problema do tipo de elenco dinamicamente. Eu sei que poderia usar reinterpret_cast, mas isso não garante o mesmo tipo de segurança.

O que devo usar para verificar se meu tipo de tipo de tipo é o mesmo tipo? Eu poderia comparar os dois tipos, mas teria um problema quando quero digitar um derivado de sua base. Então, como posso resolver isso?

Foi útil?

Solução

Eu tive que fazer coisas semelhantes ao carregar DLLs C ++ em aplicativos escritos em idiomas que suportam apenas uma interface C. Aqui está uma solução que fornecerá um erro imediato se um tipo de objeto inesperado foi passado. Isso pode facilitar as coisas quando algo der errado.

O truque é que todas as classes que você desmaiam como alça precisam herdar de uma classe base comum.

#include <stdexcept>
#include <typeinfo>
#include <string>
#include <iostream>
using namespace std;


// Any class that needs to be passed out as a handle must inherit from this class.
// Use virtual inheritance if needed in multiple inheritance situations.
class Base
{

public:
    virtual ~Base() {} // Ensure a v-table exists for RTTI/dynamic_cast to work
};


class ClassA : public Base
{

};

class ClassB : public Base
{

};

class ClassC
{
public:
    virtual ~ClassC() {}
};

// Convert a pointer to a long handle.  Always use this function
// to pass handles to outside code.  It ensures that T does derive
// from Base, and that things work properly in a multiple inheritance
// situation.
template <typename T>
long pointer_to_handle_cast(T ptr)
{
    return reinterpret_cast<long>(static_cast<Base*>(ptr));
}

// Convert a long handle back to a pointer.  This makes sure at
// compile time that T does derive from Base.  Throws an exception
// if handle is NULL, or a pointer to a non-rtti object, or a pointer
// to a class not convertable to T.
template <typename T>
T safe_handle_cast(long handle)
{
    if (handle == NULL)
        throw invalid_argument(string("Error casting null pointer to ") + (typeid(T).name()));

    Base *base = static_cast<T>(NULL); // Check at compile time that T converts to a Base *
    base = reinterpret_cast<Base *>(handle);
    T result = NULL;

    try
    {
        result = dynamic_cast<T>(base);
    }
    catch(__non_rtti_object &)
    {
        throw invalid_argument(string("Error casting non-rtti object to ") + (typeid(T).name()));
    }

    if (!result)
        throw invalid_argument(string("Error casting pointer to ") + typeid(*base).name() + " to " + (typeid(T).name()));

    return result;
}

int main()
{
    ClassA *a = new ClassA();
    ClassB *b = new ClassB();
    ClassC *c = new ClassC();
    long d = 0; 


    long ahandle = pointer_to_handle_cast(a);
    long bhandle = pointer_to_handle_cast(b);
    // long chandle = pointer_to_handle_cast(c); //Won't compile
    long chandle = reinterpret_cast<long>(c);
    // long dhandle = pointer_to_handle_cast(&d); Won't compile
    long dhandle = reinterpret_cast<long>(&d);

    // send handle to library
    //...
    // get handle back
    try
    {
        a = safe_handle_cast<ClassA *>(ahandle);
        //a = safe_handle_cast<ClassA *>(bhandle); // fails at runtime
        //a = safe_handle_cast<ClassA *>(chandle); // fails at runtime
        //a = safe_handle_cast<ClassA *>(dhandle); // fails at runtime
        //a = safe_handle_cast<ClassA *>(NULL); // fails at runtime
        //c = safe_handle_cast<ClassC *>(chandle); // Won't compile
    }
    catch (invalid_argument &ex)
    {
        cout << ex.what() << endl;
    }

    return 0;
}

Outras dicas

dynamic_cast pode ser usado apenas entre classes relacionadas através da herança. Para converter um ponteiro em longo ou vice-versa, você pode usar reinterpret_cast. Para verificar se o ponteiro é nulo, você pode assert(ptr != 0). No entanto, geralmente não é aconselhável usar reinterpret_cast. Por que você precisa converter um ponteiro por muito tempo?

Outra opção é usar um sindicato:

union  U { 
int* i_ptr_;
long l;
}

Novamente, a união também é necessária apenas raramente.

Lembre-se de que no Windows 64, um ponteiro será uma quantidade de 64 bits, mas long ainda será uma quantidade de 32 bits e seu código está quebrado. No mínimo, você precisa fazer a escolha do tipo inteiro com base na plataforma. Não sei se o MSVC tem suporte para uintptr_t, o tipo fornecido em C99 para segurar indicadores; Esse seria o melhor tipo a ser usado se estiver disponível.

Quanto ao resto, outros abordaram o porquê e o que está de dynamic_cast vs. reinterpret_cast suficientemente.

reinterpret_cast é o elenco correto a ser usado aqui.

Este é praticamente o coisa que pode fazer com segurança.

reinterpret_cast de um tipo de ponteiro para um tipo T e de volta ao tipo de ponteiro original produz o ponteiro original. (Assumindo que T é um ponteiro ou tipo inteiro que é pelo menos tão grande quanto o tipo de ponteiro original)

Observe que reinterpret_cast de um tipo de ponteiro para t não é especificado. Não há garantias sobre o valor do tipo t, exceto que, se você reinterprete o tipo de volta ao tipo original, obterá o valor original. Portanto, supondo que você não tente fazer nada com o valor longo intermediário no seu caso, reinterpret_cast é perfeitamente seguro e portátil.

EDIT: Obviamente, isso não ajuda se você não souber no segundo elenco, qual era o tipo original. Nesse caso, você está ferrado. O Long não pode, de qualquer forma, transportar informações sobre qual ponteiro foi convertido.

Você pode usar reinterpret_cast para lançar para um tipo integral e voltar ao tipo de ponteiro. Se O tipo integral é grande o suficiente para armazenar o valor do ponteiro, então essa conversão não alterará o valor do ponteiro.

Como outros já dizem, não é um comportamento definido para usar o Dynamic_cast em uma classe não polimórfica (exceto quando você faz um upcast, que está implícito de qualquer maneira e é ignorado aqui), e também funciona apenas em ponteiros ou referências. Não em tipos integrais.

É melhor você usar ::intptr_t encontrado em vários sistemas POSIX. Você pode usar esse tipo como seu tipo intermediário para o qual for lançado.

Em relação à sua verificação se a conversão será bem -sucedida, você pode usar o tamanho de:

BOOST_STATIC_ASSERT(sizeof(T1) >= sizeof(T2));

falhará no momento da compilação se a conversão não puder ser feita. Ou continue a usar afirmar com essa condição e ela afirmará em tempo de execução.

Aviso: Isso não vai impedir você de lançar T* para intptr_t de volta a U* com você outro tipo que T. Assim, isso apenas garante que o elenco não mude o valor do ponteiro se você for lançado T* para intptr_t e de volta para T*. (Graças a Nicola apontando, você pode esperar outra proteção).

O que você quer fazer parece uma ideia muito ruim e perigosa, mas se você deve fazê -lo (ou seja, está trabalhando em um sistema herdado ou em hardware que você sabe que nunca mudará), então eu sugiro envolver o ponteiro em alguns tipo de estrutura simples que contém dois membros: 1) um ponteiro vazio para a instância do seu objeto e uma string, enum ou algum outro tipo de identificador exclusivo que lhe dirá o que lançar o vazio original*. Aqui está um exemplo do que eu quis dizer (Nota: não me preocupei em testá -lo para que possa haver erros sintáticos):

struct PtrWrapper {
  void* m_theRealPointer;
  std::string m_type;
};

void YourDangerousMethod( long argument ) {

   if ( !argument ) 
     return;

   PtrWrapper& pw = *(PtrWrapper*)argument;

   assert( !pw.m_type.empty() );

   if ( pw.m_type == "ClassA" ) {
     ClassA* a = (ClassA*)pw.m_theRealPointer;
     a->DoSomething();
   } else if (...) { ... }

}

dynamic_cast<> é um elenco destinado a ser usado apenas em conversível tipos (no sentido polimórfico). Forçando o elenco de um pointer para um long (Litb sugere corretamente o static_assert para garantir a compatibilidade do tamanho) todas as informações sobre o tipo de ponteiro estão perdidos. Não há como implementar um safe_reinterpret_cast<> Para obter o ponteiro de volta: valor e tipo.

Para esclarecer o que quero dizer:

struct a_kind {}; 
struct b_kind {}; 

void function(long ptr) 
{} 

int 
main(int argc, char *argv[]) 
{ 
    a_kind * ptr1 = new a_kind; 
    b_kind * ptr2 = new b_kind;

    function( (long)ptr1 );
    function( (long)ptr2 );

    return 0;
}

Não há como function() Para determinar o tipo de ponteiro que passou e "Down" o lançar para o tipo adequado, a menos que seja:

  • O longo é envolvido por um objeto com algumas informações do tipo.
  • O tipo em si é codificado no objeto referenciado.

Ambas as soluções são feias e devem ser evitadas, pois são substitutos da RTTI.

Além disso, melhor use size_t em vez de um longo - acho que esse tipo é garantido para ser compatível com o tamanho do espaço de endereço.

Assim que você decidiu lançar um ponteiro por muito tempo, você jogou a segurança do tipo para o vento.

Dynamic_cast é usado para lançar uma árvore de derivação. Ou seja, de um ponteiro de classe base a um ponteiro de classe derivado. Se você tem:

class Base
{
};

class Foo : public  Base
{
};

class Bar : public Base
{
};

Você pode usar dinâmico_cast dessa maneira ...

Base* obj = new Bar;

Bar* bar = dynamic_cast<Bar*>(obj); // this returns a pointer to the derived type because obj actually is a 'Bar' object
assert( bar != 0 );

Foo* foo = dynamic_cast<Foo*>(obj);  // this returns NULL because obj isn't a Foo
assert( foo == 0 );

... Mas você não pode usar o elenco dinâmico para lançar para uma árvore de derivação. Você precisa reinterpretest_cast ou elenco de estilo C para isso.

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