例如:
class Base1 {};
class Base2 {};
class Derived: publid Base1, public Base2 {};
// object is stored on a void* slot
void* void_slot = new Derived();
// ... many decades after ...
//object is fetched from the void* slot
Base2* obj = (Base2*) void_slot;
obj->some_base2_method();
我认为这可能不安全。 dynamic_cast<>
是否解决了这个问题?
Base2* obj = dynamic_cast<Base2*> void_slot;
更多背景:
我正致力于从Perl调用C ++库。构造C ++对象时,它存储在Perl值的整数槽中(IV
的{{1}}值),类似于SV
;当您调用方法时,对象指针从void*
强制转换,并使用对象指针调用相应的C ++方法。因此我猜它可能有问题,因为指向基类型的指针可能与指向派生类型的指针不同,特别是当存在多个继承时。
我在PerlMonks上发布了一个类似的问题,但没有得到很多回复。所以我在这里问一下,从C ++的角度来看。
答案 0 :(得分:5)
是的,它不安全,但由于empty base optimization,您的示例可能不会导致错误。请考虑以下示例:
class Base1 { int b1; };
class Base2 { int b2; };
class Derived : public Base1, public Base2 { int d; };
Derived
类型对象的内存布局可能如下所示:
0123456789AB
[b1][b2][ d]
^ begin of Derived
^ begin of Base1
^ begin of Base2
现在,指向Derived
和Base1
的指针将具有相同的数值,但Base2
的指针将不同。要适当更改数值,编译器必须知道您要将Derived*
转换为Base2*
。将其投放到void*
之间时,这是不可能的,因为void*
的价值也可能来自Base2*
。
事实上,像static_cast<T*>(static_cast<void*>(x))
这样的转换序列正是reinterpret_cast<T*>(x)
的定义方式。并且你不会认为reinterpret_cast
随机使用任意类型是安全的 - 你会吗?
虽然人们可能认为dynamic_cast
可能对此有所帮助,但实际上甚至不适用!由于dynamic_cast
应该使用运行时类型信息来保证可以进行强制转换,因此其目标需要是具有至少一个虚拟成员的类类型的指针(或引用)。在这种情况下,目标甚至不是指向完整类型的指针,而是指向void
。
无论你之后做什么,必须检索你存储的相同类型的指针(唯一的例外是将对象解释为char
数组)。显而易见的解决方案是,始终存储指向公共基类的指针,如
void* void_slot = static_cast<CommonBase*>(input);
CommonBase* output = static_cast<CommonBase*>(void_slot);
或者使用一个知道你正在谈论哪种指针的中间类
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;
}
答案 1 :(得分:3)
如果您完全控制了类,只需创建一个虚拟根目录即可。在转换为void *
之前先转换为第一个,然后再回到第一个。然后,您可以使用dynamic_cast
转换为您想要的任何派生类型:
struct Root {
virtual ~Root() {}
};
struct Base1 : virtual public Root { };
struct Base2 : virtual public Root { };
struct Derived1 : public Base1, public Base2 { };
struct Derived2 : public Derived1 { };
int main() {
Derived1 *d1 = new Derived1;
Derived2 *d2 = new Derived2;
void *vp = static_cast<Root *>(d1);
Derived1 *d11 = dynamic_cast<Derived1 *>(static_cast<Root *>(vp));
vp = static_cast<Root *>(d2);
Derived2 *d22 = dynamic_cast<Derived2 *>(static_cast<Root *>(vp));
delete d1;
delete d2;
}
编辑:显然这些类必须是多态的,你必须使用dynamic_cast,所以在Root中放入一个简单的虚拟析构函数。