简体   繁体   English

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

[英]Is it unsafe to store derived object to void*, then cast base object out of it?

For example: 例如:

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();

I think it is probably unsafe. 我认为这可能不安全。 Does dynamic_cast<> solve this issue? dynamic_cast<>是否解决了这个问题?

Base2* obj = dynamic_cast<Base2*> void_slot;

More backgrounds : 更多背景

I'm working on calling C++ library from Perl. 我正在从Perl调用C ++库。 When you construct an C++ object, it is stored in the integer slot of a Perl value (the IV value of a SV ), which is like a void* ; 构造C ++对象时,它存储在Perl值的整数槽中( SVIV值),就像void* ; and when you call methods, the object pointer is cast from IV , and the corresponding C++ method is called using the object pointer. 当你调用方法时,对象指针从IV ,并使用对象指针调用相应的C ++方法。 Thus I guess it could be problematic, as the pointer to base type can be different with the pointer to derived type, especially when there are multiple inheritance. 因此我猜它可能有问题,因为指向基类型的指针可能与指向派生类型的指针不同,尤其是当存在多个继承时。

I've posted a similar question on PerlMonks, but did not get much response from there. 我在PerlMonks上发布了一个类似的问题,但没有得到太多响应。 So I ask it here, from the aspect of C++. 所以我在这里问一下,从C ++的角度来看。

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: Derived类型的对象的内存布局可能如下所示:

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. 现在,指向DerivedBase1的指针将具有相同的数值,但对Base2的指针将有所不同。 To change the numerical value appropriately, the compiler has to know that you are converting a Derived* to a Base2* . 要适当地更改数值,编译器必须知道您正在将Derived*转换为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* . 铸造它的时候,这是不可能void*之间,因为值void*也可以同样都来自一个Base2*

In fact, a conversion sequence like static_cast<T*>(static_cast<void*>(x)) is exactly how reinterpret_cast<T*>(x) is defined. 实际上,像static_cast<T*>(static_cast<void*>(x))这样的转换序列正是如何定义reinterpret_cast<T*>(x)的。 And you would not assume that reinterpret_cast is safe to randomly use an arbitrary types - would you? 并且你不会认为reinterpret_cast随机使用任意类型是安全的 - 你会吗?

What about dynamic_cast? 那dynamic_cast怎么样?

While one might believe that dynamic_cast might help here, it is in fact not even applicable! 虽然有人可能认为dynamic_cast可能对此有所帮助,但实际上它甚至都不适用! 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. 由于dynamic_cast应该使用运行时类型信息来保证可以进行强制转换,因此其目标需要是具有至少一个虚拟成员的类类型的指针(或引用)。 In this case, the target is not even a pointer to a complete type, but to void . 在这种情况下,目标甚至不是指向完整类型的指针,而是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). 无论你以后做什么,你必须检索你存储的相同类型的指针(唯一的例外是将对象解释为char数组)。 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;
}

If you have complete control over your classes, just create a single virtual root base. 如果您可以完全控制类,只需创建一个虚拟根目录。 Cast to that first before you cast to void * , then cast back to that first. 在转换为void *之前先转换为第一个,然后再转换回第一个。 Then you can use dynamic_cast to cast to whatever derived type you want: 然后,您可以使用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;
}

EDIT: Apparently the classes must be polymorphic and you must use dynamic_cast, so put a trivial virtual destructor in Root. 编辑:显然这些类必须是多态的,你必须使用dynamic_cast,所以在Root中放入一个简单的虚拟析构函数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM