给定基类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
编写单独的函数,那么一切正常。但是,如果我可以在某种程度上使用基类,那么它的样板代码就会少得多。
答案 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 *
的转换不是纯粹概念性的,但实际上需要对指针值进行物理更改。在典型的实现中,这通常发生在:
B
不是多态的,包含非零大小的子对象,而类Ai
是多态的。 (不是你的情况)Ai
有多个基础,B
只是其中之一。我的猜测是你正在处理代码中的第二种情况。
在这种情况下,如果您确保基础B
是 Ai
的第一个基础,它可能会“正常”(但同样,这是大量实施 - 依赖,显然,不可靠)。
答案 2 :(得分:0)
我提出了以下工作解决方案,它避免了双方都有2 * N个函数,其中N是派生A
类的数量。相反,它涉及两侧各有两个功能。对象库有一个包含N个案例的开关,它将void*
强制转换为适当的类。请注意,g ++方面确实只需要了解enum
,但仍然不了解类型。
不确定它是否是完美的方法,但看起来非常简洁和安全。仍对其他解决方案/评论感兴趣。
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回答。