Yes it is unsafe, but will probably cause no errors in your example due to the empty base optimization. Consider instead the following example:
class Base1 { int b1; };
class Base2 { int b2; };
class Derived : public Base1, public Base2 { int d; };
The memory layout for an object of type Derived
will probably look like this:
0123456789AB
[b1][b2][ d]
^ begin of Derived
^ begin of Base1
^ begin of Base2
Now, a pointer to Derived
and to Base1
will have the same numerical value, but one to Base2
will be different. To change the numerical value appropriately, the compiler has to know that you are converting a Derived*
to a Base2*
. This is not possible when casting it to void*
in between, since the value of the void*
could just as well have come from a Base2*
.
In fact, a conversion sequence like static_cast<T*>(static_cast<void*>(x))
is exactly how reinterpret_cast<T*>(x)
is defined. And you would not assume that reinterpret_cast
is safe to randomly use an arbitrary types - would you?
What about dynamic_cast?
While one might believe that dynamic_cast
might help here, it is in fact not even applicable! Since dynamic_cast
is supposed to use run time type information to guarantee that a cast is possible, its target needs to be a pointer (or reference) to a class type with at least one virtual member. In this case, the target is not even a pointer to a complete type, but to void
.
How to deal with the conundrum?
No matter what you do afterwards you must retrieve the same type of pointer that you stored (with a sole exception for interpreting your object as a char
array). The obvious solution would be, to either always store a pointer to a common base class like
void* void_slot = static_cast<CommonBase*>(input);
CommonBase* output = static_cast<CommonBase*>(void_slot);
or to use an intermediate class that knows which kind of pointer you are talking about
struct Slotty {
enum class type_t {
Base1,
Base2,
Derived
} type;
void* ptr;
Slotty(Base1* ptr) : type(type_t::Base1), ptr(ptr) { }
Slotty(Base2* ptr) : type(type_t::Base2), ptr(ptr) { }
Slotty(Derived* ptr) : type(type_t::Derived), ptr(ptr) { }
};
void* void_slot = static_cast<void*>(new Slotty(input));
Slotty* temp = static_cast<Slotty*>(void_slot);
switch(Slotty.type) {
case Slotty::type_t::Base1:
/* do sth with */ static_cast<Base1*>(temp.ptr);
break;
case Slotty::type_t::Base2:
/* do sth with */ static_cast<Base2*>(temp.ptr);
break;
case Slotty::type_t::Derived:
/* do sth with */ static_cast<Derived*>(temp.ptr);
break;
}