7

我做了一个测试代码如下:

#include <iostream>
using namespace std;

#ifndef interface
#define interface struct
#endif

interface Base
{
    virtual void funcBase() = 0;
};

interface Derived1 : public Base
{
    virtual void funcDerived1() = 0;
};

interface Derived2 : public Base
{
    virtual void funcDerived2() = 0;
};

interface DDerived : public Derived1, public Derived2
{
    virtual void funcDDerived() = 0;
};

class Implementation : public DDerived
{
public:
    void funcBase() { cout << "base" << endl; }
    void funcDerived1() { cout << "derived1" << endl; }
    void funcDerived2() { cout << "derived2" << endl; }
    void funcDDerived() { cout << "dderived" << endl; }
};

int main()
{
    DDerived *pObject = new Implementation;
    pObject->funcBase();

    return 0;
}

我写这段代码的原因是为了测试函数 funcBase() 是否可以在 DDerived 的实例中调用。当我尝试编译此代码时,我的 C++ 编译器 (Visual Studio 2010) 给了我一条编译错误消息。在我看来,这段代码没有问题,因为它肯定funcBase()会在接口的某个派生类中实现(因此被覆盖)DDerived,因为它是纯虚拟的。换句话说,任何类型的指针变量都Implementation *应该与派生实现并覆盖函数的类的实例相关联funcBase()

我的问题是,为什么编译器会给我这样的错误信息?为什么 C++ 语法是这样定义的;即,将这种情况视为错误?如何使代码运行?我想允许接口的多重继承。当然,如果我使用“虚拟公共”或重新声明funcBase()Implementation功能

interface DDerived : public Derived1, public Derived2
{
    virtual void funcBase() = 0;
    virtual void funcDDerived() = 0;
};

然后一切运行没有问题。

但是我不想那样做,寻找更方便的方法,因为虚拟继承可能会降低性能,如果类的继承关系非常复杂,重新声明会很繁琐。除了使用虚拟继承之外,还有其他方法可以在 C++ 中启用接口的多重继承吗?

4

4 回答 4

7

正如您定义的那样,您的对象结构如下所示:

在此处输入图像描述

这里重要的一点是,每个实例都Implementation包含两个完全独立的实例Base。您提供了 的覆盖Base::funcBase,但它不知道您是尝试覆盖您继承的funcBase,还是您继承的。BaseDerived1BaseDerived2

是的,处理这个问题的干净方法是虚拟继承。这将改变您的结构,因此只有一个 Base 实例:

在此处输入图像描述

这几乎毫无疑问是你真正想要的。是的,在原始编译器和 25 MHz 486 等时代,它因性能问题而闻名。使用现代编译器和处理器,您不太可能遇到问题。

另一种可能性是某种基于模板的替代方案,但这往往会遍及您的代码的其余部分——即,Base *您编写的模板可以与提供函数 A、B 和 C 的任何东西一起使用,而不是传递 a,然后将(相当于)Implementation作为模板参数传递。

于 2012-04-27T15:25:16.760 回答
3

C++ 语言的设计方式是,在没有虚拟继承的第一种方法中,将有该方法的两个父副本,并且它无法确定调用哪个父副本。

虚拟继承是从多个基继承相同函数的 C++ 解决方案,所以我建议只使用这种方法。

或者,您是否考虑过不从多个基础继承相同的功能?你真的有一个派生类,你需要能够根据上下文将其视为Derived1Derived2OR吗?Base

在这种情况下,详细说明具体问题而不是人为的示例可能有助于提供更好的设计。

于 2012-04-27T15:16:34.767 回答
1
DDerived *pObject = new Implementation;
pObject->funcBase();

这会创建一个指向实现的 DDerived 类型的指针。当您使用 DDerived 时,您实际上只有一个指向接口的指针。

DDerived 不知道 funcBase 的实现,因为在 Derived1 和 Derived2 中都定义了 funcBase 不明确。

这创造了一个继承钻石,这才是真正导致问题的原因。

http://en.wikipedia.org/wiki/Diamond_problem

我还必须检查你在那里的界面“关键字”

这是 Visual Studio 识别的特定于 ms 的扩展

于 2012-04-27T15:21:21.047 回答
1

我认为 C++ Standard 10.1.4 - 10.1.5 可以帮助您理解代码中的问题。

class L { public: int next; /∗ ... ∗/ };
class A : public L { /∗...∗/ };
class B : public L { /∗...∗/ };
class C : public A, public B { void f(); /∗ ... ∗/ };

10.1.4包含关键字 virtual的基类说明符指定非虚拟基类。包含关键字 virtual 的基类说明符指定虚拟基类。对于最派生类的类格中非虚拟基类的每次不同出现,最派生对象(1.8)应包含该类型的相应不同基类子对象。对于指定为虚拟的每个不同的基类,最派生的对象应包含该类型的单个基类子对象。[示例: 对于类类型 C 的对象,在 C 的类格中每个不同的(非虚拟)基类 L 与类型 C 对象内的不同 L 子对象一一对应。给定类 C 定义上面,C 类的对象将有 L 类的两个子对象,如下所示。

10.1.5在这样的格中,可以使用显式限定来指定所指的子对象。函数 C::f 的主体可以引用每个 L 子对象的成员 next: void C::f() { A::next = B::next; } // 格式良好的. 如果没有 A:: 或 B:: 限定符,上面的 C::f 的定义会因为歧义而格式错误

因此,只需在调用 pObject->funcBase() 时添加限定符或以其他方式解决歧义。

pObject->Derived1::funcBase();

更新:阅读10.3 Virtual Functions of Standard也很有帮助。

周末愉快 :)

于 2012-04-27T15:31:48.210 回答