c++ - 将派生对象存储到 void*,然后从中转换基对象是否不安全?

标签 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 值的整数槽中(IVSV 值),类似于 void*。 ;当你调用方法时,对象指针是从 IV 转换而来的,并使用对象指针调用相应的 C++ 方法。因此我猜这可能是有问题的,因为指向基类型的指针可能与指向派生类型的指针不同,尤其是在存在多重继承时。

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

最佳答案

是的,它是不安全的,但由于 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 在这里可能有所帮助,但实际上它甚至不适用!由于 dynamic_cast 应该使用运行时类型信息来保证转换是可能的,因此它的目标需要是指向具有至少一个虚拟成员的类类型的指针(或引用)。在这种情况下,目标甚至不是指向 complete 类型的指针,而是指向 void 的指针。

如何应对难题?

无论您之后做什么,您都必须检索与您存储的相同类型的指针(唯一的异常(exception)是将您的对象解释为 char 数组)。显而易见的解决方案是,要么始终存储指向公共(public)基类的指针,例如

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;
}

关于c++ - 将派生对象存储到 void*,然后从中转换基对象是否不安全?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23451982/

相关文章:

c++ - 从构造函数调用构造函数

c++ - 将 std::vector<T> move 到 T*

c++ - wglCreateContextAttribsARB 在 x64 平台下的 Debug模式下崩溃

java - 构造函数重载 - Java 最佳实践

Java:对于UML图中仅存在于父类(super class)中的抽象方法应该做什么?

javascript - 如何扩展谷歌地图类?

c++ - "Expected Class name"继承错误

c++ - QtabBar 文本和图标

c++ - 运算符返回冲突的大小

c++ - 使用 C++ 在 MySQL 中为 MFC Dialog Base App 插入一行