【问题标题】:multiple inheritance: unexpected result after cast from void * to 2nd base class多重继承:从 void * 转换为第二个基类后的意外结果
【发布时间】:2011-01-23 16:18:06
【问题描述】:

我的程序需要使用 void* 在动态调用情况下传输数据或对象,以便它可以引用任意类型的数据,甚至是原始类型的数据。但是,我最近发现,在具有多个基类的类的情况下,向下转换这些 void* 的过程会失败,甚至在对这些向下转换的指针调用方法后使我的程序崩溃,即使内存地址似乎是正确的。在访问“vtable”期间发生崩溃。

所以我创建了一个小测试用例,环境是 Mac OS X 上的 gcc 4.2:

class Shape {
public:
    virtual int w() = 0;
    virtual int h() = 0;
};

class Square : public Shape {
public:
    int l;
    int w() {return l;}
    int h() {return l;}
};

class Decorated {
public:
    int padding;
    int w() {return 2*padding;}
    int h() {return 2*padding;}
};

class DecoratedSquare : public Square, public Decorated {
public:
    int w() {return Square::w() + Decorated::w();}
    int h() {return Square::h() + Decorated::h();}
};


#include <iostream>

template <class T> T shape_cast(void *vp) {
//    return dynamic_cast<T>(vp);   // not possible, no pointer to class type
//    return static_cast<T>(vp);
//    return T(vp);
//    return (T)vp;
    return reinterpret_cast<T>(vp);
}

int main(int argc, char *argv[]) {
    DecoratedSquare *ds = new DecoratedSquare;
    ds->l = 20;
    ds->padding = 5;
    void *dsvp = ds;

    std::cout << "Decorated (direct)" << ds->w() << "," << ds->h() << std::endl;

    std::cout << "Shape " << shape_cast<Shape*>(dsvp)->w() << "," << shape_cast<Shape*>(dsvp)->h() << std::endl;
    std::cout << "Square " << shape_cast<Square*>(dsvp)->w() << "," << shape_cast<Square*>(dsvp)->h() << std::endl;
    std::cout << "Decorated (per void*) " << shape_cast<Decorated*>(dsvp)->w() << "," << shape_cast<Decorated*>(dsvp)->h() << std::endl;
    std::cout << "DecoratedSquare " << shape_cast<DecoratedSquare*>(dsvp)->w() << "," << shape_cast<DecoratedSquare*>(dsvp)->h() << std::endl;
}

产生以下输出:

Decorated (direct)30,30
Shape 30,30
Square 30,30
Decorated (per void*) 73952,73952
DecoratedSquare 30,30

如您所见,“Decorated (per void*)”结果是完全错误的。它也应该是 30,30,就像第一行一样。

无论我在 shape_cast() 中使用什么铸造方法,我总是会为 Decorated 部分获得相同的意外结果。这些 void * 完全有问题。

根据我对 C++ 的理解,这应该是可行的。有没有机会让它与 void* 一起工作?这可能是 gcc 中的错误吗?

谢谢

【问题讨论】:

  • 您只能使用 reinterpret_cast 强制转换为 void* 然后再返回原始类型。您可以NOT 转换为 void*,然后转换为其他任何东西。
  • 使用装饰器模式会比 MI 好得多,但这并不能解决从 void* 转换的问题。

标签: c++ gcc multiple-inheritance casting void-pointers


【解决方案1】:

重复十次 - 您可以安全地使用 reinterpret_cast 指针做的唯一事情是 reinterpret_cast 将它返回到它来自的相同指针类型。这同样适用于转换为 void*:您必须转换回原始类型。

因此,如果您将 DecoratedSquare* 转换为 void*,则必须将其转换回 DecoratedSquare*。不是Decorated*,不是Square*,不是Shape*。其中一些可能在您的机器上工作,但这是好运和特定于实现的行为的结合。它通常适用于单继承,因为没有明显的理由以会阻止其工作的方式实现对象指针,但这不能保证,并且它通常不适用于多继承。

您说您的代码通过 void* 访问“任意类型,包括原始类型”。这并没有错——大概接收数据的人都知道将其视为DecoratedSquare*,而不是int*

如果接收者只知道将其视为基类,例如Decorated*,那么将其转换为void*的人应先将static_cast转换为基类,然后再转换为void*

void *decorated_vp = static_cast<Decorated*>(ds);

现在,当您将decorated_vp 转换回Decorated* 时,您将得到static_cast&lt;Decorated*&gt;(ds) 的结果,这就是您所需要的。

【讨论】:

  • 史蒂夫对此表示感谢。我非常总结。实际上,始终确保在框架的调用部分中有一个已知的基指针类型,该类型被强制转换为 void* 然后传输到接收端,接收端知道两件事:最顶层的基类和某个方法所在的子类被定义为。但是,它不知道具体的子类。但是现在这似乎没问题,因为接收端总是重新解释到与发送端相同的最顶层基类,然后对调用所需方法的中间子类执行 dynamic_cast 或 static_cast。
  • +1 表示“您可以安全地使用 reinterpret_cast 指针做的唯一事情是将它重新解释为它来自的相同指针类型。”确实很好地总结了情况。
【解决方案2】:

这不是编译器错误 - 这是 reinterpret_cast 所做的。 DecoratedSquare 对象将在内存中布局如下:

Square
Decorated
DecoratedSquare specific stuff

将指向 this 的指针转换为 void* 将给出该数据的起始地址,而不知道那里是什么类型。 reinterpret_cast&lt;Decorated*&gt; 将获取该地址并将其中的任何内容解释为Decorated - 但实际内存内容是Square。这是错误的,因此您会得到未定义的行为。

如果您将reinterpret_cast 转换为正确的动态类型(即DecoratedSquare),您应该会得到正确的结果,然后转换为基类。

【讨论】:

  • C++ 中的 MI 是完整的,除非你扔掉类型信息。如果你不疯狂地转换为错误的类型,你可以使用virtual 函数在不知道具体子类的情况下获得多态行为。
  • @Andre,这不是多重继承的问题,而是将事物转换为 void* 的问题。有效的方法是将指针转换为 void*,然后返回其原始 static 类型(不像 Mike 暗示的那样动态)。将 void* 转换为其他东西是未定义的(嗯,有一些例外)
  • 您可以使用reinterpret_castShape 后跟dynamic_castDecorated 来做您想做的事情。不过,我不是 100% 确定,所以我不会将其添加到答案中。它很可能会给出未定义的行为。
  • 构建框架的自然方式是使用模板或抽象类型。 void* 是一种做低级工作的方式,不应该在你的框架中可见。
  • @Mike,这里原来的静态类型是 DecoratedSquare,所以 void* 应该被强制转换为 DecoratedSquare。如果原始静态类型是 Shape*,则 void* 应转换为 Shape*(然后最终动态转换为 DecoratedSquare)。
【解决方案3】:

存在多重继承的 static_cast 或 dynamic_cast 可能会通过偏移指针来更改指针的表示,以便它指定正确的地址。 static_cast 通过考虑静态类型信息来确定正确的偏移量。 dynamic_cast 通过检查动态类型来做到这一点。如果你抛出 void*,你将失去所有静态类型信息和获取动态类型信息的可能性,所以你使用的 reinterpret_cast 假设偏移量为空,有时会失败。

【讨论】:

  • 那么这是否意味着 void* 在多重继承情况下不可行?
  • 是可行的,只要你总是转换为对象的实际类型,而不是基类。
  • 查看我对 Mike 的回答的评论
  • 更准确地说,您必须将 void* 转换为该值在转换为 void* 之前的静态类型。
  • 不仅如此,static_cast 或隐式转换也可能通过读取 vptr 或其他实现替代方案来使用动态类型信息。
猜你喜欢
  • 1970-01-01
  • 2019-06-09
  • 2018-06-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-03-10
相关资源
最近更新 更多