我正在创建几个旨在提供对回调功能的访问的接口(interface)。也就是说,从接口(interface)A继承允许一个类使用类型一的回调; 接口(interface) B 允许类型二。从 A 和 B 继承允许两种类型的回调。最终目的是类 A 和 B 将通过继承它们来处理所有脏工作。
第一个问题
这是一个小例子,可以说明我遇到的一些问题:
class A
{
public:
static void AFoo( void* inst )
{
((A*)inst)->ABar( );
}
virtual void ABar( void ) = 0;
};
class B
{
public:
static void BFoo( void* inst )
{
((B*)inst)->BBar( );
}
virtual void BBar( void ) = 0;
};
class C : public A, public B
{
public:
void ABar( void ){ cout << "A"; };
void BBar( void ){ cout << "B"; };
};
通过打电话
C* c_inst = new C( );
void (*AFoo) (void*) = C::AFoo;
void (*BFoo) (void*) = C::BFoo;
AFoo( (void*)c_inst );
BFoo( (void*)c_inst );
我希望我会得到“AB”作为输出。相反,我得到“AA”。反转派生类的顺序(B 在 A 之前),生成“BB”。这是为什么?
第二个问题
我使用的实际界面是模板化的,所以代码看起来更像
template <class T> class A
{
public:
static void AFoo( void* inst )
{
((T*)inst)->ABar( );
}
virtual void ABar( void ) = 0;
};
template <class T> class B
{
public:
static void BFoo( void* inst )
{
((T*)inst)->BBar( );
}
virtual void BBar( void ) = 0;
};
class C : public A<C>, public B<C>
{
public:
void ABar( void ){ cout << "A"; };
void BBar( void ){ cout << "B"; };
};
这样做的原因是 A 和 B 可以完成所有工作,但他们的实现不需要了解 C 的任何知识。
现在,调用
C* c_inst = new C( );
void (*AFoo) (void*) = C::AFoo;
void (*BFoo) (void*) = C::BFoo;
AFoo( (void*)c_inst );
BFoo( (void*)c_inst );
产生正确的输出:“AB”。
这个小例子在这里运行良好,但在实践中并不总是正确运行。非常奇怪的事情开始发生,类似于上面第一个问题中的怪异。主要问题似乎是两个虚函数(或静态函数,或其他)并不总能进入 C。
例如,我可以成功调用 C::AFoo(),但并不总是能成功调用 C::BFoo()。这有时取决于我从 A 和 B 派生的顺序:class C: public A<C>, public B<C>
可能会生成 AFoo 或 BFoo 都不起作用的代码,而 class C: public B<C>, public A<C>
可能会在其中一个工作的地方生成代码,或者可能在两个地方都工作。
由于类是模板化的,我可以删除 A 和 B 中的虚函数。这样做会生成工作代码,当然只要 ABar 和 BBar 存在于 C 中。这是可以接受的,但不是我们想要的;我更想知道问题出在哪里。
上面的代码可能会出现什么奇怪的问题?
为什么第二个例子产生了正确的输出,而第一个例子却没有?
最佳答案
您正在调用未定义的行为。您可以将 X*
转换为 void*
,但是一旦您这样做了,唯一安全的方法就是转换 void*
> 是一个 X*
(这不完全正确,我过于简单化了,但为了争论假装它是)。
现在为什么代码的行为是这样的?实现 MI 的一种方法类似于:
struct A
{
A_vtable* vtbl;
};
struct B
{
B_vtable* vtbl;
};
struct C
{
struct A;
struct B;
};
在此示例中,A 在前,但顺序将由编译器确定。当您转换为 void 时,您会得到一个指向 C 开头的指针。当您将 void* 转换回来时,您已经丢失了必要时适当调整指针所需的信息。由于 A 和 B 都有一个具有相同签名的虚函数,因此您最终调用了 impl.无论哪个类恰好是对象布局中的第一个。
关于C++ 多重继承 + 虚函数(- 歧义)= 奇怪的行为(也是函数指针),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9106288/