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

时间:2010-03-04 13:07:57

标签: c++ gcc multiple-inheritance casting 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;
}

产生以下输出:

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

正如您所看到的,“装饰(每个空白*)”结果完全错误。它应该像第一行中的30,30一样。

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

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

由于

3 个答案:

答案 0 :(得分: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);

现在,当您将decorated_vp投回Decorated*时,您将获得static_cast<Decorated*>(ds)的结果,这就是您所需要的。

答案 1 :(得分:9)

这不是编译器错误 - 它是reinterpret_cast的作用。 DecoratedSquare对象将在内存中布置如下:

Square
Decorated
DecoratedSquare specific stuff

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

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

答案 2 :(得分:2)

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