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

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

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

Ответ 1

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

Ответ 2

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

Ответ 3

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

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

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

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

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

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

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

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

Существует много API, которые требуют, чтобы вы передали void* на некоторый объект, например. java/Jni Code передает объект как 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;
}

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

Ответ 4

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

Ответ 5

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

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

Ответ 6

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

Windows очень много использует Opaque Pointers в своем API. HANDLE, как мне кажется, обычно является непрозрачным указателем на фактический ресурс, например, для HANDLE. HANDLE может быть ядром. Объекты, такие как файлы, объекты 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";
}

Ответ 7

Не делайте это дома

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

и нет способа заставить его работать.