C++多重继承+虚函数( - 模糊)=奇怪的行为(也是函数指针)

Chr*_*ers 2 c++ inheritance pointers casting static-cast

我正在创建一些旨在提供对回调功能的访问的接口.也就是说,继承自接口A允许类使用类型1的回调; 接口B允许类型2.继承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"; };
};
Run Code Online (Sandbox Code Playgroud)

通过拨打电话

C* c_inst = new C( );
void (*AFoo) (void*) = C::AFoo;
void (*BFoo) (void*) = C::BFoo;
AFoo( (void*)c_inst );
BFoo( (void*)c_inst );
Run Code Online (Sandbox Code Playgroud)

我希望我能得到"AB"作为输出.相反,我得到"AA".颠倒派生类的顺序(A之前的B),产生"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"; };
};
Run Code Online (Sandbox Code Playgroud)

这样做的原因是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 );
Run Code Online (Sandbox Code Playgroud)

产生正确的输出:"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中的虚函数.这样就产生了工作代码,只要AB当然存在于C语言中.这是可以接受的,但不是所希望的; 我宁愿知道问题所在.

上述代码可能导致奇怪问题的原因有哪些?

为什么第二个例子产生正确的输出,尽管第一个没有?

Log*_*ldo 5

您正在调用未定义的行为.你可以把一个转换X*为a void*,但是一旦你完成了这个,那么唯一可以安全的void*就是X*(这不完全正确,我过于简单化了,但是为了争论而假装它是).

现在为什么代码表现得像?实现MI的一种方法类似于:

 struct A
 {
    A_vtable* vtbl;
 };

 struct B
 {
    B_vtable* vtbl;
 };

 struct C
 {
    struct A;
    struct B;
 };
Run Code Online (Sandbox Code Playgroud)

在此示例中,A是第一个,但顺序将由编译器确定.当你转换为void时,你会得到一个指向C开头的指针.当你向后转换那个void*时,你已经失去了在必要时适当调整指针所需的信息.由于A和B都有一个具有相同签名的虚函数,因此最终调用impl.在对象布局中首先出现的是哪个类.