虚拟继承:当只有一个基类具有“虚拟”关键字时,为什么它可以工作?有没有更好的办法?

时间:2014-12-27 20:40:41

标签: c++ inheritance

我想在C ++中实现一个类层次结构:

  • 我需要接口,所以我可以提供多种实现。
  • 我需要在所有类中使用常用方法。但我需要能够覆盖特定的方法。
  • 构造函数都至少采用一个参数。

简化我有这个代码:

#include <iostream>

class IClass {
public:
    virtual int commonMethod() const = 0;
};

class Class : public virtual IClass {
protected:
    int commonValue;

public:
    Class(int commonValue) : commonValue(commonValue) {}

    virtual int commonMethod() const {
        return commonValue;
    }
};


class IClassDerived : public virtual IClass {
public:
    virtual void specialMethod() = 0;
};

class ClassDerived : public Class, public virtual IClassDerived {
public:
    ClassDerived(int commonValue) : Class(commonValue) {}

    virtual void specialMethod() {
        // do something
    }
};


class IClassDerived2 : public virtual IClassDerived {
public:
    virtual void specialMethod2() = 0;
};

class ClassDerived2 : public ClassDerived, public virtual IClassDerived2 {
public:
    ClassDerived2(int commonValue) : ClassDerived(commonValue) {}

    virtual void specialMethod2() {
        specialMethod();
    }
};


class IClassDerived3 : public virtual IClassDerived2 {
public:
    virtual int commonMethod() const override = 0;
};

class ClassDerived3 : public ClassDerived2, public virtual IClassDerived3 {
public:
    ClassDerived3(int commonValue) : ClassDerived2(commonValue) {}

    virtual int commonMethod() const override {
        return 4711;
    }
};


int main() {
    ClassDerived foo(1);
    ClassDerived2 foo2(2);
    ClassDerived3 foo3(3);

    std::cout << foo.commonMethod() << " " << foo2.commonMethod() << " " << foo3.commonMethod() << " " << std::endl;
    // 1 2 4711

    return 0;
}

我现在有两个问题:

  • 为什么这个有效?
    • 如果我尝试没有虚拟继承,我会收到错误“'specialMethod'含糊不清”和“...因为以下虚函数在'ClassDerived'中是纯粹的:virtual int IClass :: commonMethod()const”。由于两个基类,每个成员都有两次导致这些错误。确定。
    • 如果我进行虚拟继承并使用“public virtual”指定两个基类,我会得到“没有匹配函数来调用'Class :: Class()'”。研究表明,在虚拟继承的情况下,我需要一个基类的默认构造函数。
    • 通过反复试验,我找到了上述解决方案。但我不明白为什么它到目前为止有效。当只有一个基类是“虚拟”而不是另一个基类时会发生什么?
  • 有更好的方法吗?我设法得到这个小例子来编译,但实际上我的课程更复杂,我担心这只适用于这个小片段而且我没有看到这可能带来的未来问题......

2 个答案:

答案 0 :(得分:4)

我最近发现了一种不需要虚拟继承的解决方法(见下文)。

基本上,在这种情况下,语言需要使用虚拟继承来直接解决问题,因为您从同一个类继承了多次。没有虚拟继承,你最终会得到这个:

Interface0  Interface0  Interface0
    ^           ^           ^______
    |           |                  \
Interface1  Interface1              Impl0
    ^           ^__________________   ^
    |                              \  |
Interface2                          Impl1
    ^______________________________   ^
                                   \  |
                                    Impl2

有多个&#34;实例&#34; InterfaceX基类的,它们是独立的。考虑路径Interface0中的Impl1 -> Interface1 -> Interface0实例。 Impl0类不继承自 Interface0的实例,因此它不实现其虚函数。请注意,如果所有这些接口类都是有状态类(使用数据成员)而不是纯接口,那么这很有用。

但是在这种特殊情况下,你只从接口继承,理论上理论上不需要虚拟继承。我们想看到以下图片:

Interface0 _
    ^     |\
    |       \
Interface1 _ Impl0
    ^     |\   ^
    |       \  |
Interface2 _ Impl1
          |\   ^
            \  |
             Impl2

理论上,Impl1可以定义一个单独的vtable,其中Impl0的条目实现Interface0的虚函数,Impl1的函数实现Interface1的虚函数{1}}。结果将是单个vtable,不需要偏移计算(因此不需要虚拟继承)。

但是,唉,语言并没有这样定义继承 - 它在抽象类和纯接口之间没有区别。如果通过虚拟继承继承它们,它允许您仅通过侧向继承(Impl0覆盖Impl1 -> Interface1 -> Interface0的虚函数)覆盖虚函数。通过虚拟继承,您指定确实只从Interface0继承了一次,因此从Impl1Interface0的两条路径(直接继承和Impl0 })yield 相同的类。

虚拟继承有几个缺点,因为它必须允许只能在运行时确定基类(相对于子对象)的位置的情况。 但是,有一种不需要虚拟继承的解决方法。

首先编写一个自包含的类,然后将其调整为接口通常更有用。接口通常由周围的体系结构定义,因此不一定是该类的通用。通过将接口与实现分离,您可以使用不同的接口重用实现。

如果我们把它放在一起:我们不能使用多重继承来实现虚函数的方法,我们希望将接口与实现分开。我们最终得出:要么,我们不要让我们的实现来自任何东西(这会导致样板代码),或者我们从线性推导出来,这是顶部接口的唯一继承。

通过将我们的实现类编写为类模板,我们可以线性派生并传递要从基础实现类派生的顶层接口:

struct Interface0 {
    virtual void fun0() = 0;
};

struct Interface1 : Interface1 {
    virtual void fun1() = 0;
};

struct Interface2 : Interface0 {
    virtual void fun2() = 0;
};


template<typename Interface = Interface0>
struct Impl0 : Interface {
    void fun0() {}
};

template<typename Interface = Interface1>
struct Impl1 : Impl0<Interface> {
    void fun1() {}
};

template<typename Interface = Interface2>
struct Impl2 : Impl1<Interface> {
    void fun2() {}
};


int main()
{
    auto x = Impl2<Interface2>(); // or simply: Impl2<>()
    Interface2* p = &x;
}

请注意,我们使用继承:在Impl1方面实现Impl0,并将扩展接口传递给我们的基类。

mainImpl2<>中的案例:

Interface0
    ^
    |
Interface1
    ^
    |
Interface2 _
          |\
            \
             Impl0<Interface2>
               ^
               |
             Impl1<Interface2>
               ^
               |
             Impl2<Interface2>

另一个案例,Impl1<>

Interface0
    ^
    |
Interface1 _
          |\
            \
             Impl0<Interface1>
               ^
               |
             Impl1<Interface1>

答案 1 :(得分:2)

您必须在所有派生类构造函数中初始化虚拟基类。因此,如果你有

class ClassDerived : public virtual Class, public virtual IClassDerived {
//                          ^^^^^^^

然后在ClassDerived2ClassDerived3的构造函数中,您必须初始化Class,尽管它似乎是在ClassDerived的构造函数中初始化的:

ClassDerived2(int commonValue) : Class(commonValue), ClassDerived(commonValue)  {}
ClassDerived3(int commonValue) : Class(commonValue), ClassDerived2(commonValue) {}

原因是[class.base.init]/7

  

mem-initializer ,其中 mem-initializer-id 表示虚拟基类在执行任何不是派生类最多的类的构造函数时被忽略


关于是否有更好的方法,我个人宁愿使用聚合而不使用虚拟继承,尽管它需要编写一些样板转发代码:

class IClass {
public:
    virtual int commonMethod() const = 0;
};

class Class : public IClass {
protected:
    int commonValue;

public:
    Class(int commonValue) : commonValue(commonValue) {}

    virtual int commonMethod() const {
        return commonValue;
    }
};


class IClassDerived : public IClass {
public:
    virtual void specialMethod() = 0;
};

class ClassDerived : public IClassDerived { // no inheritance from Class
public:
    ClassDerived(int commonValue) : m_class(commonValue) {}

    virtual int commonMethod() const {
        return m_class.commonMethod();
    }

    virtual void specialMethod() {
        // do something
    }

private:
    Class m_class;
};

// and so on