多重继承:从void*转换为第二个基类后的意外结果

And*_*eis 8 c++ gcc casting multiple-inheritance void-pointers

我的程序需要使用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;
}
Run Code Online (Sandbox Code Playgroud)

产生以下输出:

Decorated (direct)30,30
Shape 30,30
Square 30,30
Decorated (per void*) 73952,73952
DecoratedSquare 30,30
Run Code Online (Sandbox Code Playgroud)

如您所见,"装饰(每个空*)"结果完全错误.它应该像第一行中的30,30一样.

无论我在shape_cast()中使用什么演员方法,我总会得到装饰部分相同的意外结果.这些无效的东西是完全错误的.

根据我对C++的理解,这实际上应该是有效的.有没有机会让这个与void*一起工作?这可能是gcc中的错误吗?

谢谢

Ste*_*sop 12

重复十次 - 你可以安全地使用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);
Run Code Online (Sandbox Code Playgroud)

现在当你decorated_vp回归时Decorated*,你会得到static_cast<Decorated*>(ds)你需要的结果.


Mik*_*our 9

这不是编译器错误 - 它是什么reinterpret_cast.该DecoratedSquare对象将在内存中布局如下:

Square
Decorated
DecoratedSquare specific stuff
Run Code Online (Sandbox Code Playgroud)

将指针转换为此指针void*将提供此数据的起始地址,而不知道存在哪种类型.reinterpret_cast<Decorated*>将获取该地址并解释其中的任何Decorated内容 - 但实际的内存内容是Square.这是错误的,因此您会得到未定义的行为.

如果您reinterpret_cast使用正确的动态类型(即DecoratedSquare),则应获得正确的结果,然后转换为基类.

  • 除非你丢弃类型信息,否则C++中的MI是完整的.如果不对错误的类型进行粗暴操作,则可以使用`virtual`函数来获取多态行为,而无需了解具体的子类. (2认同)
  • @Andre,它不是多重继承的问题,它是一个将事物转化为void*的问题.什么工作是将指针转换为void*然后回到其原始的*static*类型(不像Mike似乎暗示的动态).将void*转换为其他内容是未定义的(好吧,有一些例外) (2认同)
  • 您可以使用`reinterpret_cast`到`Shape`然后``dynamic_cast`到`Decorated`来做你想要的.我不是100%肯定,所以我不会把它添加到答案中.它可能会给出未定义的行为. (2认同)
  • @Mike,这里的原始静态类型是DecoratedSquare,所以void*应该被转换为DecoratedSquare.如果原始静态类型是Shape*,则void*应该被转换为Shape*(最终然后动态地转换为DecoratedSquare). (2认同)