Pregunta

Para un sistema que necesita para convertir un puntero a un tiempo, entonces el tiempo de vuelta para el tipo de puntero.Como se puede adivinar esto es muy peligroso.Lo que yo quería hacer es usar dynamic_cast para hacer la conversión por lo que si he mezclado les voy a obtener un puntero null.Esta página dice http://publib.boulder.ibm.com/infocenter/lnxpcomp/v7v91/index.jsp?topic=/com.ibm.vacpp7l.doc/language/ref/clrc05keyword_dynamic_cast.htm

El operador dynamic_cast realiza las conversiones de tipos en tiempo de ejecución.El operador dynamic_cast garantiza la la conversión de un puntero a una base de clase a un puntero a una clase derivada, o la conversión de un lvalue refiriéndose a una clase base a un referencia a una clase derivada.Un el programa puede, por tanto, el uso de una clase jerarquía de forma segura.Este operador y el operador typeid proporcionar en tiempo de ejecución tipo de información (RTTI) apoyo en la C++.

y me gustaría conseguir un error si es null, así que escribí mi propia conversión dinámica

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

Con MSVC me sale el error "error C2681:'largo' :no válido tipo de expresión para dynamic_cast".Resulta que esto sólo funcionará con las clases que tienen funciones virtuales...WTF!Yo sé que el propósito de una dinámica de reparto fue para el casting de la herencia problema, pero también pensé que era para resolver el tipo de fundición problema de forma dinámica.Sé que podría utilizar reinterpret_cast pero eso no garantiza el mismo tipo de seguridad.

¿Qué debo usar para comprobar si mi encasillado son del mismo tipo?Podría comparar las dos typeid pero me gustaría tener un problema cuando quiero convertir un derivado de su base.Entonces, ¿cómo puedo solucionar esto?

¿Fue útil?

Solución

He tenido que hacer cosas similares al cargar archivos DLL de C ++ en aplicaciones escritas en lenguajes que solo admiten una interfaz C. Aquí hay una solución que le dará un error inmediato si se pasa un tipo de objeto inesperado. Esto puede hacer que las cosas sean mucho más fáciles de diagnosticar cuando algo sale mal.

El truco es que cada clase que pases como identificador debe heredar de una clase base común.

#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;
}

Otros consejos

dynamic_cast solo se puede usar entre clases relacionadas mediante herencia. Para convertir un puntero a largo o viceversa, puede usar reinterpret_cast. Para verificar si el puntero es nulo, puede assert(ptr != 0). Sin embargo, generalmente no es aconsejable usar <=>. ¿Por qué necesita convertir un puntero a largo?

Otra opción es usar una unión:

union  U { 
int* i_ptr_;
long l;
}

Nuevamente, la unión también se necesita raramente.

Recuerde que en Windows 64, un puntero será una cantidad de 64 bits pero long seguirá siendo una cantidad de 32 bits y su código está roto. Como mínimo, debe elegir el tipo de entero según la plataforma. No sé si MSVC tiene soporte para uintptr_t, el tipo proporcionado en C99 para contener punteros; ese sería el mejor tipo para usar si está disponible.

En cuanto al resto, otros han abordado suficientemente los por qué y los por qué de dynamic_cast vs reinterpret_cast.

reinterpret_cast es el reparto correcto para usar aquí.

Esto es más o menos lo único que puede hacer de manera segura.

reinterpret_cast de un tipo de puntero a un tipo T y de vuelta al tipo de puntero original produce el puntero original. (Suponiendo que T es un tipo de puntero o entero que es al menos tan grande como el tipo de puntero original)

Tenga en cuenta que reinterpret_cast de un tipo de puntero a T no está especificado. No hay garantías sobre el valor del tipo T, excepto que si luego lo reinterpreta_cast nuevamente al tipo original, obtiene el valor original. Entonces, suponiendo que no intente hacer nada con el valor largo intermedio en su caso, reinterpret_cast es perfectamente seguro y portátil.

Editar: Por supuesto, esto no ayuda si no sabes en el segundo elenco, cuál era el tipo original. En ese caso, estás jodido. El largo no puede de ninguna manera llevar información de tipo sobre qué puntero se convirtió.

Puede utilizar reinterpret_cast para la conversión a una integral del tipo y de vuelta a el tipo de puntero. Si el tipo integral es lo suficientemente grande para almacenar el valor del puntero, luego de que la conversión no va a cambiar el valor del puntero.

Como otros ya digo, no está definido el comportamiento de uso de dynamic_cast en un no polimórficas de la clase (excepto cuando se hace una upcast, que es implícita de todos modos y ser ignorado aquí), y también sólo funciona en punteros o referencias.No en los tipos integrales.

Es mejor usar ::intptr_t encontrado en diversos sistemas posix.Usted puede utilizar ese tipo como su intermedio tipo de yeso para.

Con respecto a su comprobar si la conversión va a tener éxito, usted puede usar sizeof:

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

se producirá un error en tiempo de compilación si la conversión no se podía hacer.O continuar el uso de afirmar con esa condición, y se hará valer en tiempo de ejecución en lugar.

Advertencia: Esto no impedirá que lances T* a intptr_t de vuelta a U* con U otro tipo de T.Por lo tanto, esta solo garantiza el reparto no va a cambiar el valor del puntero si se convierte de T* a intptr_t y de vuelta a T*.(Gracias a Nicola señalando puede esperar otra protección).

Lo que quieres hacer parece una idea realmente mala y peligrosa, pero si DEBES hacerlo (es decir, estás trabajando en un sistema heredado o en un hardware que sabes que nunca cambiará), entonces te sugiero que termines puntero en algún tipo de estructura simple que contiene dos miembros: 1) un puntero vacío a su instancia de objeto y una cadena, enumeración o algún otro tipo de identificador único que le dirá a qué vaciar el vacío * original. Aquí hay un ejemplo de lo que quise decir (nota: no me molesté en probar esto, por lo que puede haber errores sintácticos):

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<> es un molde destinado a ser utilizado solo en tipos convertibles (en el sentido polimórfico). Al forzar la conversión de un pointer a un long (litb sugiere correctamente el static_assert para garantizar la compatibilidad del tamaño), se pierde toda la información sobre el tipo del puntero . No hay forma de implementar un safe_reinterpret_cast<> para recuperar el puntero: tanto el valor como el tipo.

Para aclarar lo que quiero decir:

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

No hay forma de que function() determine el tipo de puntero pasado y & "; abajo &"; emitirlo al tipo adecuado, a menos que:

  • el largo está envuelto por un objeto con alguna información del tipo.
  • el tipo en sí está codificado en el objeto referenciado.

Ambas soluciones son feas y deben evitarse, ya que son sustitutos de RTTI.

también, mejor use size_t en lugar de long. Creo que este tipo está garantizado para ser compatible con el tamaño del espacio de direcciones.

Tan pronto como decidiste lanzar un puntero a un largo, arrojaste el tipo de seguridad al viento.

dynamic_cast se usa para lanzar & amp; abajo un árbol de derivación. Es decir, de un puntero de clase base a un puntero de clase derivado. Si tienes:

class Base
{
};

class Foo : public  Base
{
};

class Bar : public Base
{
};

Puede usar dynamic_cast de esta manera ...

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

... pero no puede usar la conversión dinámica para transmitir dentro de un árbol de derivación. Necesita reinterpret_cast o lanzamientos de estilo C para eso.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top