Есть ли практическое применение для динамического приведения к пустому указателю? - PullRequest
72 голосов
/ 14 ноября 2011

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

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

Ответы [ 7 ]

69 голосов
/ 14 ноября 2011

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
7 голосов
/ 14 ноября 2011

Имейте в виду, что 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*>, но это использование.

4 голосов
/ 16 ноября 2011

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

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

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

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

Обычно вы хотите использовать 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;
}

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

1 голос
/ 09 августа 2012

Расширяя ответ @ 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
1 голос
/ 20 ноября 2011

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

0 голосов
/ 23 ноября 2011

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

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

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

0 голосов
/ 22 ноября 2011

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

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