Есть ли практическое применение для динамического приведения к пустому указателю?
В C++ T q = dynamic_cast<T>(p);
конструкция выполняет приведение указателя во время выполнения p
к другому типу указателя T
который должен появиться в иерархии наследования динамического типа *p
чтобы добиться успеха. Это все хорошо и хорошо.
Тем не менее, также возможно выполнить dynamic_cast<void*>(p)
, который просто вернет указатель на "самый производный объект" (см. 5.2.7::7 в C++11). Я понимаю, что эта функция, вероятно, выходит бесплатно при реализации динамического приведения, но полезна ли она на практике? В конце концов, его тип возврата в лучшем случае void*
Так что же хорошего в этом?
7 ответов
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 должен возвращать ноль для недопустимого приведения типов, а стандарт также гарантирует, что любой указатель должен иметь возможность типизировать приведение к void * и обратно из него, за исключением только указателей на функции.
Нормальный уровень практического применения очень меньше для void*
Typecasting, но он широко используется в низкоуровневых / встроенных системах.
Обычно вы хотели бы использовать reinterpret_cast для вещей низкого уровня, как в 8086, он используется для смещения указателя той же базы, чтобы получить адрес, но не ограничивается этим.
Изменить: Стандарт говорит, что вы можете конвертировать любой указатель на 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
Это может быть одним из способов предоставления непрозрачного указателя через ABI. Непрозрачные указатели - и, в более общем смысле, непрозрачные типы данных - используются для передачи объектов и других ресурсов между библиотечным кодом и клиентским кодом таким образом, чтобы клиентский код можно было изолировать от деталей реализации библиотеки. Конечно, есть и другие способы сделать это, и, возможно, некоторые из них будут лучше для конкретного случая использования.
Windows широко использует Opaque Pointers в своем 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";
}
Не делай этого дома
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 * оператор new (size_t); оператор void delete (void*); };
и нет способа заставить это работать.