将派生对象存储到void *是不安全的,然后将基础对象转换出来吗?

时间:2014-05-04 02:35:39

标签: c++ inheritance

例如:

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 ++的角度来看。

2 个答案:

答案 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

现在,指向DerivedBase1的指针将具有相同的数值,但Base2的指针将不同。要适当更改数值,编译器必须知道您要将Derived*转换为Base2*。将其投放到void*之间时,这是不可能的,因为void*的价值也可能来自Base2*

事实上,像static_cast<T*>(static_cast<void*>(x))这样的转换序列正是reinterpret_cast<T*>(x)的定义方式。并且你不会认为reinterpret_cast随机使用任意类型是安全的 - 你会吗?

dynamic_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中放入一个简单的虚拟析构函数。