C++“处理多个基类的虚拟函数”

Posted

技术标签:

【中文标题】C++“处理多个基类的虚拟函数”【英文标题】:C++ "Virtual functions handling on multiple base classes" 【发布时间】:2012-08-27 17:59:53 【问题描述】:

我需要从两个不同的基类CBaseACBaseB派生一个子类CDerived

另外,我需要在派生类上调用双亲的虚函数。因为我想稍后在一个向量中管理不同类型的对象(这不是这个最小代码示例的一部分),我需要从基类指针调用虚拟函数到派生类对象:

#include <iostream>
#include <stdlib.h>

class CBaseA

  public:
    virtual void FuncA() std::cout << "CBaseA::FuncA()" << std::endl; ;
;

class CBaseB

  public:
    virtual void FuncB() std::cout << "CBaseB::FuncB()" << std::endl; ;
;

class CDerived : public CBaseB, public CBaseA
;

int main( int argc, char* argv[] )

  // An object of the derived type:
  CDerived oDerived;

  // A base class pointer to the object, as it could later
  // be stored in a general vector:
  CBaseA* pAHandle = reinterpret_cast<CBaseA*>( &oDerived );

  // Calling method A:
  pAHandle->FuncA();

  return 0; 

问题:但在我的计算机上运行时,调用的是 FuncB() 而不是 FuncA()。如果我“翻转”父类声明,我会得到正确的结果,即

class CDerived : public CBaseA, public CBaseB

但这并不能解决我的问题,因为我无法确定将调用哪个函数。

所以我的问题是: 我做错了什么以及处理此类问题的正确方法是什么?

(顺便说一下,我使用的是 g++ 4.6.2)

【问题讨论】:

【参考方案1】:
CBaseA* pAHandle = reinterpret_cast<CBaseA*>( &oDerived );

不要使用reinterpret_cast 执行到基类的转换。不需要演员表;转换是隐式的:

CBaseA* pAHandle = &oDerived;

要转换为派生类,如果已知对象属于目标类型,则使用static_cast;如果不是,则使用dynamic_cast

您对reinterpret_cast 的使用会产生未定义的行为,因此您会看到“奇怪”的行为。 reinterpret_cast 的正确用法很少,而且它们都不涉及类层次结构中的转换。

【讨论】:

事实上,没有正确使用 reinterpret_cast 不涉及在对它进行任何操作之前将其转换回旧类型。 @DirkHolsople:还有其他有效的用途。例如,任何对象都可以重新解释为字节数组。 我指的是便携式使用。将对象重新解释为字节数组本质上是特定于平台的。 标准涵盖的有趣转换:5.2.10.4 & 5:将指针转换为整数类型并返回。 5.2.10.11:将T 对象转换为T&amp;T&amp;&amp;。 9.2.20:“指向标准布局结构对象的指针,使用 reinterpret_cast 适当转换,指向其初始成员(或者如果该成员是位字段,则指向它所在的单元),反之亦然。 " 26.4.4: 对复杂 类型使用 reinterpret_cast。 3.9.2 也暗示了(并给出了理由)James McNellis 的例子。【参考方案2】:

常见的实现,可以帮助您了解发生了什么。

内存中的CBaseA是这样的

+---------+
| __vptrA |
+---------+

内存中的CBaseB是这样的

+---------+
| __vptrB |
+---------+

CDrived 看起来像这样:

             +---------+
&oDerived->  | __vptrB |
             | __vptrA |
             +---------+

如果您只是将 &oDerived 分配给 CBaseA*,编译器会放置代码来添加偏移量,以便您拥有

             +---------+
&oDerived--->| __vptrB |
pAHandle---->| __vptrA |
             +---------+

在执行过程中,程序在 __vptrA 中找到指向 A 虚函数的指针。如果您将 static_cast 或 dynamic_cast pAHandle 返回到 CDerived(甚至 dynamic_cast pAHandle 到 CBaseA),编译器会将代码减去偏移量,以便结果指向对象的开头(dynamic_cast 会找到关于减去 vtable 以及指向虚函数的指针)。

当你将 &oDerived 重新解释为 CBaseA* 时,编译器不会放置这样的代码来调整指针,你会得到

                       +---------+
pAHandle, &oDerived--->| __vptrB |
                       | __vptrA |
                       +---------+

在执行过程中,程序在 __vptrB 中查找 A 虚函数,却找到了 B 虚函数。

【讨论】:

谢谢,这有助于我更好地理解类指针转换问题。这条信息在互联网上很难找到。

以上是关于C++“处理多个基类的虚拟函数”的主要内容,如果未能解决你的问题,请参考以下文章

调用非虚拟基方法时,C++ 中的虚拟继承是不是有任何惩罚/成本?

防止 C++ 中的虚拟方法实现

使用 CSerial (C++) 打开虚拟 COM 端口

C# 如何在 C++ 不允许虚拟模板方法的情况下允许虚拟泛型方法?

C++ 虚拟继承内存布局

如何在 Linux 上创建虚拟 CAN 端口? (C++)