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

jia*_*zhe 2 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();
Run Code Online (Sandbox Code Playgroud)

我认为这可能不安全.不dynamic_cast<>解决这个问题?

Base2* obj = dynamic_cast<Base2*> void_slot;
Run Code Online (Sandbox Code Playgroud)

更多背景:

我正在从Perl调用C++库.构造C++对象时,它存储在Perl值的整数槽中(a的IVSV),就像a void*; 当您调用方法时,将从中转换对象指针IV,并使用对象指针调用相应的C++方法.因此我猜它可能有问题,因为指向基类型的指针可能与指向派生类型的指针不同,尤其是当存在多个继承时.

我在PerlMonks上发布了一个类似的问题,但没有得到太多响应.所以我在这里问一下,从C++的角度来看.

gha*_*.st 5

是的,它不安全,但由于空基优化,可能不会在您的示例中造成错误.请考虑以下示例:

class Base1 { int b1; };
class Base2 { int b2; };
class Derived : public Base1, public Base2 { int d; };
Run Code Online (Sandbox Code Playgroud)

类型对象的内存布局Derived可能如下所示:

0123456789AB   
[b1][b2][ d]
^ begin of Derived
^ begin of Base1
    ^ begin of Base2
Run Code Online (Sandbox Code Playgroud)

现在,指向Derived和的指针Base1将具有相同的数值,但其中一个Base2将是不同的.要适当地更改数值,编译器必须知道您Derived*要将a 转换为a Base2*.将它投射到void*中间时是不可能的,因为它的价值void*也可以来自a 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);
Run Code Online (Sandbox Code Playgroud)

或者使用一个知道你正在谈论哪种指针的中间类

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;
}
Run Code Online (Sandbox Code Playgroud)