¿Hay usos prácticos para dynamic-casting a void pointer?


En C++, la construcción T q = dynamic_cast<T>(p); realiza un cast en tiempo de ejecución de un puntero p a algún otro tipo de puntero T que debe aparecer en la jerarquía de herencia del tipo dinámico de *p para tener éxito. Todo eso está muy bien.

Sin embargo, también es posible realizar dynamic_cast<void*>(p), que simplemente devolverá un puntero al "objeto más derivado" (ver 5.2.7::7 en C++11). Entiendo que esta característica probablemente salga de forma gratuita en la implementación del elenco dinámico, pero ¿es útil en la práctica? Después de todo, su tipo de retorno es en el mejor de los casos void*, así que ¿de qué sirve esto?

Author: Kerrek SB, 2011-11-14

7 answers

El dynamic_cast<void*>() de hecho se puede utilizar para comprobar la identidad, incluso si se trata de herencia múltiple.

Prueba este código:

#include <iostream>

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

class D1 : public B {
};

class D2 : public B {
};

class DD : public D1, public D2 {
};

namespace {
    bool eq(B* b1, B* b2) {
        return b1 == b2;
    }

    bool eqdc(B* b1, B *b2) {
        return dynamic_cast<void*>(b1) == dynamic_cast<void*>(b2);
    }
};

int
main() {
    DD *dd = new DD();
    D1 *d1 = dynamic_cast<D1*>(dd);
    D2 *d2 = dynamic_cast<D2*>(dd);

    std::cout << "eq: " << eq(d1, d2) << ", eqdc: " << eqdc(d1, d2) << "\n";
    return 0;
}

Salida:

eq: 0, eqdc: 1
 68
Author: mitchnull,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-14 16:01:37

Tenga en cuenta que C++ le permite hacer las cosas a la antigua C.

Supongamos que tengo alguna API en la que me veo obligado a pasar de contrabando un puntero de objeto a través del tipo void*, pero donde la devolución de llamada a la que finalmente se pasa conocerá su tipo dinámico:

struct BaseClass {
    typedef void(*callback_type)(void*);
    virtual callback_type get_callback(void) = 0;
    virtual ~BaseClass() {}
};

struct ActualType: BaseClass {
    callback_type get_callback(void) { return my_callback; }

    static void my_callback(void *p) {
        ActualType *self = static_cast<ActualType*>(p);
        ...
    }
};

void register_callback(BaseClass *p) {
   // service.register_listener(p->get_callback(), p); // WRONG!
   service.register_listener(p->get_callback(), dynamic_cast<void*>(p));
}

El MAL! el código es incorrecto porque falla en presencia de herencia múltiple (y tampoco se garantiza que funcione en ausencia).

Por supuesto, la API no es muy estilo C++, e incluso el código" correcto " puede ir mal si heredar de ActualType. Así que no diría que este es un uso brillante de dynamic_cast<void*>, pero es un uso.

 7
Author: Steve Jessop,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-14 16:14:14

Fundición de punteros a void* tiene su importancia desde el C días. El lugar más adecuado está dentro del administrador de memoria del sistema operativo. Tiene que almacenar todo el puntero y el objeto de lo que creas. Al almacenarlo en void * lo generalizan para almacenar cualquier objeto en la estructura de datos del administrador de memoria que podría ser heap/B+Tree o simple arraylist.

Para simplificar, tomemos el ejemplo de crear un list de elementos genéricos(La lista contiene elementos de clases completamente diferentes). Que ser posible solo usando void*.

Standard dice que dynamic_cast debería devolver null para el casting de tipo ilegal y standard también garantiza que cualquier puntero debería ser capaz de escribir cast it to void* y back from it con la única excepción de los punteros de función.

Nivel de aplicación normal El uso práctico es muy inferior para void* encasillamiento, pero se utiliza ampliamente en sistemas de bajo nivel/embebidos.

Normalmente querrías usar reinterpret_cast para cosas de bajo nivel, como en 8086 se utiliza para desplazar el puntero de la misma base para obtener la dirección, pero no se limita a esto.

Editar: Standard dice que puede convertir cualquier puntero a void* incluso con dynamic_cast<> pero no where indica que no puede convertir el void* de nuevo al objeto.

Para la mayoría de los usos, es una calle de sentido único, pero hay algunos usos inevitables.

Solo dice que dynamic_cast<> necesita información de tipo para convertirla de nuevo al tipo solicitado.

Hay muchas API que requieren que pases void* a algún objeto, por ejemplo. el código java/Jni pasa el objeto como void*.
Sin información de tipo no se puede hacer el casting.Si está lo suficientemente seguro de que el tipo solicitado es correcto puede pedirle al compilador que haga el dynmaic_cast<> con un truco.

Mira este código:

class Base_Class {public : virtual void dummy() { cout<<"Base\n";} };
class Derived_Class: public Base_Class { int a; public: void dummy() { cout<<"Derived\n";} };
class MostDerivedObject : public Derived_Class {int b; public: void dummy() { cout<<"Most\n";} };
class AnotherMostDerivedObject : public Derived_Class {int c; public: void dummy() { cout<<"AnotherMost\n";} };

int main () {
  try {
    Base_Class * ptr_a = new Derived_Class;
    Base_Class * ptr_b = new MostDerivedObject;
    Derived_Class * ptr_c,*ptr_d;

        ptr_c = dynamic_cast< Derived_Class *>(ptr_a);
        ptr_d = dynamic_cast< Derived_Class *>(ptr_b);

        void* testDerived = dynamic_cast<void*>(ptr_c);
        void* testMost = dynamic_cast<void*>(ptr_d);
        Base_Class* tptrDerived = dynamic_cast<Derived_Class*>(static_cast<Base_Class*>(testDerived));
        tptrDerived->dummy();
        Base_Class* tptrMost = dynamic_cast<Derived_Class*>(static_cast<Base_Class*>(testMost));
        tptrMost->dummy();
        //tptrMost = dynamic_cast<AnotherMostDerivedObject*>(static_cast<Base_Class*>(testMost));
        //tptrMost->dummy(); //fails

    } catch (exception& my_ex) {cout << "Exception: " << my_ex.what();}
    system("pause");
  return 0;
}

Por favor corríjame si esto no es correcto de ninguna manera.

 4
Author: Praveen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-24 04:09:20

Es útil cuando volvemos a poner el almacenamiento en el grupo de memoria, pero solo mantenemos un puntero a la clase base. En este caso debemos averiguar la dirección original.

 1
Author: BruceAdi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-20 08:35:24

Ampliando la respuesta de @BruceAdi e inspirado por esta discusión, aquí hay una situación polimórfica que puede requerir un ajuste del puntero. Supongamos que tenemos este tipo de configuración de fábrica:

struct Base { virtual ~Base() = default; /* ... */ };
struct Derived : Base { /* ... */ };

template <typename ...Args>
Base * Factory(Args &&... args)
{
    return ::new Derived(std::forward<Args>(args)...);
}

template <typename ...Args>
Base * InplaceFactory(void * location, Args &&... args)
{
    return ::new (location) Derived(std::forward<Args>(args)...);
}

Ahora podría decir:

Base * p = Factory();

Pero, ¿cómo limpiaría esto manualmente? Necesito la dirección de memoria real para llamar a ::operator delete:

void * addr = dynamic_cast<void*>(p);

p->~Base();              // OK thanks to virtual destructor

// ::operator delete(p); // Error, wrong address!

::operator delete(addr); // OK

O podría reutilizar la memoria:

void * addr = dynamic_cast<void*>(p);
p->~Base();
p = InplaceFactory(addr, "some", "arguments");

delete p;  // OK now
 1
Author: Kerrek SB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:31

No hagas eso en casa

struct Base {
    virtual ~Base ();
};

struct D : Base {};

Base *create () {
    D *p = new D;
    return p;
}

void *destroy1 (Base *b) {
    void *p = dynamic_cast<void*> (b);
    b->~Base ();
    return p;
}

void destroy2 (void *p) {
    operator delete (p);
}

int i = (destroy2 (destroy1 (create ())), i);

Advertencia: Esto no trabajo si D se define como:

struct D : Base {
    void* operator new (size_t);
    void operator delete (void*);
};

Y no hay manera de hacer que funcione.

 0
Author: curiousguy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-24 02:07:39

Esta podría ser una forma de proporcionar un Puntero Opaco a través de un ABI. Los Punteros opacos are y, más generalmente, Los Tipos de datos opacos are se utilizan para pasar objetos y otros recursos entre el código de la biblioteca y el código del cliente de tal manera que el código del cliente se puede aislar de los detalles de implementación de la biblioteca. Hay otros formas para lograr esto, para estar seguro, y tal vez algunas de ellas serían mejores para un caso de uso particular.

Windows hace mucho uso de Punteros Opacos en su API. HANDLE es, creo, generalmente un puntero opaco al recurso real al que tiene un HANDLE, por ejemplo. HANDLE s pueden ser Objetos del Núcleo como archivos, objetos GDI y todo tipo de Objetos de Usuario de varios tipos all todos los cuales deben ser muy diferentes en la implementación, pero todos se devuelven como HANDLE al usuario.

#include <iostream>
#include <string>
#include <iomanip>
using namespace std;


/*** LIBRARY.H ***/
namespace lib
{
    typedef void* MYHANDLE;

    void        ShowObject(MYHANDLE h);
    MYHANDLE    CreateObject();
    void        DestroyObject(MYHANDLE);
};

/*** CLIENT CODE ***/
int main()
{
    for( int i = 0; i < 25; ++i )
    {
        cout << "[" << setw(2) << i << "] :";
        lib::MYHANDLE h = lib::CreateObject();
        lib::ShowObject(h);
        lib::DestroyObject(h);
        cout << "\n";
    }
}

/*** LIBRARY.CPP ***/
namespace impl
{
    class Base { public: virtual ~Base() { cout << "[~Base]"; } };
    class Foo   : public Base { public: virtual ~Foo() { cout << "[~Foo]"; } };
    class Bar   : public Base { public: virtual ~Bar() { cout << "[~Bar]"; } };
};

lib::MYHANDLE lib::CreateObject()
{
    static bool init = false;
    if( !init )
    {
        srand((unsigned)time(0));
        init = true;
    }

    if( rand() % 2 )
        return static_cast<impl::Base*>(new impl::Foo);
    else
        return static_cast<impl::Base*>(new impl::Bar);
}

void lib::DestroyObject(lib::MYHANDLE h)
{
    delete static_cast<impl::Base*>(h);
}

void lib::ShowObject(lib::MYHANDLE h)
{
    impl::Foo* foo = dynamic_cast<impl::Foo*>(static_cast<impl::Base*>(h));
    impl::Bar* bar = dynamic_cast<impl::Bar*>(static_cast<impl::Base*>(h));

    if( foo ) 
        cout << "FOO";
    if( bar )
        cout << "BAR";
}
 0
Author: John Dibling,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-26 00:45:31