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

标签 c++ pointers inheritance casting static-cast

我正在创建几个旨在提供对回调功能的访问的接口(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/

相关文章:

c++ - 参数中的指针和引用

c# - 建模 "I' m a * 但我也是一个 **”

javascript - JavaScript 类中使用箭头函数的继承和多态性

c++ - C 中的非常大尺寸的矩阵

C++ 静态虚拟成员?

c++ - 真实C/C++代码优秀例子的建议

c# - 如何写入内存地址?

c++ - 简单高效的C++容器,具有map和list容器的特点

c++ - C++指针在for循环中被覆盖

c++ - const 引用、作为参数的构造函数和可编译性