9

我的程序需要使用 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

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

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

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

谢谢

4

3 回答 3

14

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

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

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

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

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

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

于 2010-03-04T13:37:08.867 回答
9

这不是编译器错误 - 它是什么reinterpret_cast。该DecoratedSquare对象将在内存中布置,如下所示:

Square
Decorated
DecoratedSquare specific stuff

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

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

于 2010-03-04T13:23:10.420 回答
2

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

于 2010-03-04T13:16:37.143 回答