C ++:将void *转换为基类指针

时间:2017-02-22 02:09:05

标签: c++ inheritance casting void-pointers

给定基类B,例如:

    class B
    {
    public:
        virtual ~B() = default;
    public:
        virtual int f() const = 0;
    };

和一些派生类Ai: public B(i = 1,..,N),实现f(),我收到一个void*肯定持有一个派生的Ai来自外部程序的类 - 执行f()方法。

可以为每种可能的派生类型创建一个入口点,它可以正常工作:

// for each derived class Ai
void executeF(void* aPtr, int* result)
{
    auto aObjPtr = static_cast<Ai*>(aPtr);
    *result = aObjPtr->f();
}

但是,应该可以仅使用单一功能来实现相同的结果,例如:

void executeF(void* aPtr, int* result)
{
    auto bObjPtr = static_cast<B*>(aPtr); // works
    *result = bObjPtr->f(); // Access violation
}

上述案例成功,但f()的执行失败,其中&#34;访问违规&#34;在MSVC 2013中。

上述功能有问题吗?如果是这样,有没有办法通过单一功能实现任务?

我已经阅读了一些材料,声称必须将void*仅投射到它拥有的特定类(也在下面的评论中提出)。但是,此代码编译并执行正常:http://ideone.com/e0Lr6v

关于如何调用所有内容的更多背景信息:

我无法在此处提供完整的代码,因为它太长但总结为..函数executeF,对象Ai的构造函数以及库中定义的所有内容对对象A进行操作,B作为导出函数提供,仅在void*类型上运行。仅供参考,该库正在使用MSVC 2013进行编译和构建。

另一方面(R语言的包装器)是用g ++编译和构建的 - 它动态加载上面的库,导出所需的函数并调用它。这一方面唯一可用的是void*持有对象Ai - 它只是发送创建对象的请求,调用它们的方法,释放它们。

例如(示意图),创建一个A1类型的对象:

// "objects" library
void createA1(void** aObj)
{
    *a1Obj = new A1();
}

// caller library
auto const createA1func = (int(__CC *)(void**)) GetProcAddress(getDLL(), "CreateA1");
void* a1Obj = NULL;
createAFunc(a1Obj);
// ... return the a1Obj to the external environemnt to keep it around 

然后,让a1Obj左右,做一些工作:

// caller library
auto const executeFfunc = (int(__CC *)(void*, int*)) GetProcAddress(getDLL(), "executeF");
int res(0);
executeFfunc(a1Obj, &res);

因此,如果我为双方上的每种类型Ai编写单独的函数,那么一切正常。但是,如果我可以在某种程度上使用基类,那么它的样板代码就会少得多。

4 个答案:

答案 0 :(得分:2)

Ai派生自B时,指向对象的Ai部分的指针(通常)不指向与{{1}指针相同的内存地址同一个对象的一部分(特别是如果B中有数据字段)。通过B指针访问Ai通常涉及指针修正,VMT查找等,这些内容必须由正在使用的特定编译器考虑。这就是为什么您不能简单地将B*指针投射到Ai*void*并期望一切正常工作。 B*指针不是有效的B*指针,它实际上是一个B*指针,已被重新解释Ai*,而且只是不合法。

为了确保事情保持正确排列,您必须:

  • B*投放到Ai*,然后void*投放到void*。这是你要避免的。

  • 首先将Ai*投放到Ai*,然后B*投放到B*,然后void*投放到void*(然后如果您需要访问B*的非虚拟成员,可以选择B*Ai* dynamic_cast

因此,为了使其按照您想要的方式工作,请在构造对象时执行以下操作:

Ai

等等。这可以确保传递给void createA1(void** aObj) { *aObj = static_cast<B*>(new A1()); } void createA2(void** aObj) { *aObj = static_cast<B*>(new A2()); } 的所有指针都是正确的 executeF()指针,只有这样才能B* 安全地输入其接收的指针指向executeF()的{​​{1}}指针,并使用多态来访问它实际指向的任何派生类:

void*

更新:或者,特别是在处理多个派生类时,每个派生类都有多个基类,可能会或可能不会全部共享,另一个选项就是简单地包装B*个对象在void executeF(void* aPtr, int* result) { B* bObjPtr = static_cast<B*>(aPtr); *result = bObjPtr->f(); // works } 中有一个额外的字段来指示对象类型。然后,您的Ai函数可以返回指向该结构的struct指针,而不是直接返回create...()个对象,void*函数可以先将Ai转换为execute...() struct,查看其类型字段,并相应地转换对象指针:

void*

它不理想或不灵活,但它会在你对另一方的限制范围内起作用。

否则,您可以将enum AType { a1, a2 /*, ... */ }; class B { public: virtual ~B() = default; virtual int f() = 0; }; class Bx { public: virtual ~B() = default; virtual int x() = 0; }; class By { public: virtual ~B() = default; virtual int y() = 0; }; // ... class A1 : public B, public Bx { public: int f() override { return 1; } int x() override { return 1; } }; class A2 : public B, public By { public: int f() override { return 2; } int y() override { return 2; } }; // ... struct objDesc { AType type; void *obj; }; void createA1(void** aObj) { objDesc *desc = new objDesc; desc->type = a1; desc->obj = new A1(); *aObj = desc; } void createA2(void** aObj) { objDesc *desc = new objDesc; desc->type = a2; desc->obj = new A2(); *aObj = desc; } // ... void destroyObj(void* aObj) { objDesc *desc = static_cast<objDesc*>(aObj); switch (desc->type) { case a1: delete static_cast<A1*>(desc->obj); break; case a2: delete static_cast<A2*>(desc->obj); break; //.. } delete desc; } //... void executeF(void* aPtr, int* result) { objDesc *desc = static_cast<objDesc*>(aPtr); B* bObjPtr = nullptr; switch (desc->type) { case a1: bObjPtr = static_cast<A1*>(desc->obj); break; case a2: bObjPtr = static_cast<A2*>(desc->obj); break; // other classes that implement B ... } if (bObjPtr) *result = bObjPtr->f(); } void executeX(void* aPtr, int* result) { objDesc *desc = static_cast<objDesc*>(aPtr); Bx* bObjPtr = nullptr; switch (desc->type) { case a1: bObjPtr = static_cast<A1*>(desc->obj); break; // other classes that implement Bx ... } if (bObjPtr) *result = bObjPtr->x(); } void executeY(void* aPtr, int* result) { objDesc *desc = static_cast<objDesc*>(aPtr); By* bObjPtr = nullptr; switch (desc->type) { case a2: bObjPtr = static_cast<A2*>(desc->obj); break; // other classes that implement By ... } if (bObjPtr) *result = bObjPtr->y(); } // ... 替换为所有其他类必须派生自的基类,然后您可以使用{{1根据需要:

struct

答案 1 :(得分:1)

您观察到的行为只是意味着从Ai *B *的转换不是纯粹概念性的,但实际上需要对指针值进行物理更改。在典型的实现中,这通常发生在:

  1. B不是多态的,包含非零大小的子对象,而类Ai是多态的。 (不是你的情况)
  2. 班级Ai有多个基础,B只是其中之一。
  3. 我的猜测是你正在处理代码中的第二种情况。

    在这种情况下,如果您确保基础B Ai的第一个基础,它可能会“正常”(但同样,这是大量实施 - 依赖,显然,不可靠)。

答案 2 :(得分:0)

我提出了以下工作解决方案,它避免了双方都有2 * N个函数,其中N是派生A类的数量。相反,它涉及两侧各有两个功能。对象库有一个包含N个案例的开关,它将void*强制转换为适当的类。请注意,g ++方面确实只需要了解enum,但仍然不了解类型。

不确定它是否是完美的方法,但看起来非常简洁和安全。仍对其他解决方案/评论感兴趣。

http://ideone.com/enNl3f

enum AType
{
    a1 = 1, a2
};

class B
{
public:
    virtual ~B() = default;
public:
    virtual int f() const = 0;
};

class A1: public B
{
    virtual int f() const override
    {
        return 1;
    }
};
class A2: public B
{
    virtual int f() const override
    {
        return 2;
    }
};

void executeF(void* aPtr, AType aType, int* result)
{
    B* bPtr = nullptr;
    switch(aType)
    {
        case a1:
            bPtr = static_cast<A1*>(aPtr);
            break;
        case a2:
            bPtr = static_cast<A2*>(aPtr);
            break;
        default:
            break;
    }

    if(bPtr)
        *result = bPtr->f();
}

答案 3 :(得分:-2)

  

函数executeF,对象的构造函数Ai ...

这很可能是问题,你不应该在构造函数中调用虚拟。它适用于Ai,因为Ai不从vptr表调用虚方法。 B但是如果正在构建的话,它还没有这样的表。请参阅this other SO回答。