Существуют ли практические применения для динамического приведения к указателю void?


в C++T q = dynamic_cast<T>(p); конструкция выполняет приведение указателя во время выполнения p к какому-то другому типу указателя T это должно появиться в иерархии наследования динамического типа *p для того, чтобы добиться успеха. Это все прекрасно и хорошо.

однако, это также можно выполнить dynamic_cast<void*>(p), который просто вернет указатель на "самый производный объект" (см. 5.2.7::7 в C++11). Я понимаю, что эта функция, вероятно, выходит бесплатно в реализации динамический актерский состав, но полезен ли он на практике? В конце концов, его тип возврата в лучшем случае void*, так что хорошего в этом?

7 71

7 ответов:

The dynamic_cast<void*>() действительно можно использовать для проверки идентичности, даже если речь идет о множественном наследовании.

попробуйте этот код:

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

выход:

eq: 0, eqdc: 1

имейте в виду, что C++ позволяет вам делать все по-старому.

Предположим, у меня есть какой-то API, в котором я вынужден контрабандой указатель на объект через тип void*, но где обратный вызов он в конечном итоге передается будет знать его динамический тип:

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

неправильно! код неверен, потому что он не работает при наличии множественного наследования (и не гарантируется работа в отсутствие).

конечно, API не очень C++ - стиль, и даже "правильный" код может пойти не так, если я наследую от ActualType. Поэтому я бы не утверждал, что это блестящее использование dynamic_cast<void*>, но это использовать.

приведение к void* имеет свое значение с давних времен в C дней. Наиболее подходящее место находится внутри менеджера памяти операционной системы. Он должен хранить весь указатель и объект того, что вы создаете. Сохраняя его в void * они обобщают его для хранения любого объекта в структуре данных диспетчера памяти, которая может быть heap/B+Tree или просто arraylist.

для простоты возьмем пример создания list общих элементов(список содержит элементы совершенно разных учебные занятия.) Это было бы возможно только с помощью void*.

стандарт говорит, что dynamic_cast должен возвращать null для незаконного приведения типов, а стандарт также гарантирует, что любой указатель должен иметь возможность вводить его в void* и обратно из него, за исключением указателей функций.

нормальный уровень применения практическое использование очень меньше для void* typecasting но он широко используется в низкоуровневых/встроенных системах.

обычно вы хотели бы используйте reinterpret_cast для низкоуровневого материала, например, в 8086 он используется для смещения указателя той же базы, чтобы получить адрес, но не ограничивается этим.

Edit: Стандарт говорит, что вы можете преобразовать любой указатель на void* даже dynamic_cast<> но это не где говорится, что вы не можете конвертировать void* вернуться к объекту.

для большинства видов использования, его улица с односторонним движением, но есть некоторые неизбежные использования.

он просто говорит, что dynamic_cast<> должен типа информация для преобразования его обратно в запрошенный тип.

есть много API, которые требуют от вас пройти void* к некоторому объекту, например. код java/Jni передает объект как void*.
Без информации о типе вы не можете выполнить кастинг.если вы достаточно уверены, что запрошенный тип правильно вы можете попросить компилятор сделать dynmaic_cast<> С трюком.

посмотрите на этот код:

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

пожалуйста, поправьте меня, если это не правильно в любом случае.

Это полезно, когда мы помещаем хранилище обратно в пул памяти, но мы сохраняем только указатель на базовый класс. В этом случае мы должны выяснить исходный адрес.

расширяя ответ @BruceAdi и вдохновленный эта дискуссия, вот полиморфная ситуация,которая может потребовать корректировки указателя. Предположим, что у нас есть эта заводская настройка:

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

теперь я мог бы сказать:

Base * p = Factory();

но как я могу очистить это вручную? Мне нужен фактический адрес памяти для вызова ::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

или я мог бы повторно использовать память:

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

delete p;  // OK now

не делайте этого дома

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

предупреждение: это не работать, если D определено как:

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

и нет никакого способа, чтобы заставить его работать.

Это может быть один из способов обеспечить Непрозрачный Указатель через ABI. Непрозрачные указатели - и, в более общем плане,Непрозрачные Типы Данных -- используются для передачи объектов и других ресурсов между кодом библиотеки и кодом клиента таким образом, что код клиента может быть изолирован от деталей реализации библиотеки. Есть другоеспособы для этого, чтобы быть уверенным, и, возможно, некоторые из них будут лучше конкретный случай использования.

Windows много использует непрозрачные указатели в своем API. HANDLE - Это, я считаю, вообще непрозрачный указатель на фактический ресурс, у вас есть HANDLE, например. HANDLEs могут быть объектами ядра, такими как файлы, объекты GDI и всевозможные пользовательские объекты различных типов-все из которых должны сильно отличаться в реализации, но все они возвращаются как HANDLE для пользователя.

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