什么是多重继承?

时间:2013-12-19 23:43:33

标签: c++ inheritance multiple-inheritance diamond-problem

我将以下内容称为“多重继承”:

  • 通过继承一个或多个后代,直接继承一次类,间接继承一次或多次
  • 通过继承两个或多个后代来间接继承一个类

我想知道它是否存在以及如何明确地访问嵌入的子对象。

1。)[ Professional C ++ ,2 nd ed。] 声明可编辑的程序不能有一个类直接继承其直接父级和父级的父级。这是真的吗?

鉴于扩展GrandParent的{​​{1}}和Parent,VC12和g ++允许GrandParent直接从GrandChild和{{1}继承}。在VC12和g ++中,可以按如下方式定义这些类:

Parent声明GrandParent数据成员。 GrandParent除了继承int num' s Parent之外,还声明了自己的numGrandParent除了继承numGrandChild' s num之外,还声明了自己的Parent

VC12似乎允许全面明确的成员访问,但g ++仅在某些情况下允许它。

GrandParent

2。)为什么(a)num在(+)#include <iostream> using std::cout; using std::endl; struct GrandParent { int num; }; struct Parent : GrandParent { int num; }; struct GrandChild : GrandParent, Parent { int num; }; int main() { GrandChild gc; gc.num = 2; gc.Parent::num = 1; gc.Parent::GrandParent::num = 0; // g++ error: ‘GrandParent’ is an ambiguous base of ‘GrandChild’ gc.GrandParent::num = 5; // g++ error: ‘GrandParent’ is an ambiguous base of ‘GrandChild’ // --VC12 output; g++ output-- cout << gc.num << endl; // 2 ; 2 cout << gc.Parent::num << endl; // 1 ; 1 cout << gc.Parent::GrandParent::num << endl; // 0 ; N/A due to above error cout << gc.GrandParent::num << endl; // 5 ; N/A due to above error } 不是g +时是不明确的? (a)唯一地描述它在继承树上的位置。 gc.Parent::GrandParent::num只有1个gc.Parent::num子对象,只有1个gc子对象,只有1个Parent。对于(b),GrandParent有一个num,它有自己的gc,还有Parent子对象和另一个num

3.。对于GrandParent,VC12似乎会查看num的{​​{1}}基础子对象,后者gc.GrandParent::num。我猜测它是明确的,因为它是由gc限定的名称查找,因此GrandParent中首先查找num右侧的实体。gc&#39}范围,最直接的.gc范围是直接继承的范围,而不是通过GrandParent间接继承的范围。我错了吗?

4。)gc不是Parent时为什么gc.GrandParent::num对g ++不明确?如果一个人含糊不清,那么两者是否应该同样含糊不清?对于先前,gc.Parent::num有两个gc s;对于后者,GrandParent有2 Parent s。


Gregoire,Marc R.等。 Professional C ++ ,2 nd ed。印第安纳波利斯,印第安纳州:Wiley Pubishing,2011年。 241.打印。

2 个答案:

答案 0 :(得分:3)

这个常用术语是菱形图案(或diamond problem)。

这本身并不是错误,但正如此处的评论中所述,任何尝试访问层次结构中其他地方重复的直接库都会导致歧义错误。

一种解决方法是使基础间接。 C ++ 11中新的继承构造函数功能允许完美的包装器:

template< typename base, typename tag >
struct disambiguated_base : base
    { using base::base; };

给定一个未使用的标记类型,这将生成一个从给定基数派生并在功能上与其相同的新类。标签类型可以是由详细类型说明符表示的不完整类:

struct GrandChild : Parent,
    disambiguated_base< GrandParent, class grandchild_grandparent_tag > {

    typedef disambiguated_base< GrandParent, grandchild_grandparent_tag >
        my_direct_grandparent;

    int num;
};

现在GrandChild可以使用my_direct_grandparent::消除成员访问的歧义。

答案 1 :(得分:1)

我正在加入已接受的答案。它声明如果派生类也间接继承base,派生类将无法访问直接base类。它的解决方案通过用第二个类型参数为base的模板包装它来使tag类间接。这可确保base与派生类间接,前提是派生类使用唯一tag扩展包装的基类。以下示例将使用非类型tag

如果类似菱形的问题被推广为包含更多代:

  • i th 类继承自base和(i - 1) th
  • (i - 1) th 继承自base和(i - 2) th
  • ......,和
  • 2 nd 继承自base

然后它是一个临时容器,其中每个元素都存储在每个唯一标记的base中。在这种情况下,tag - 制作应该是自动化的。一种方法是通过非类型模板合并所有派生类。它的非类型参数N可以指定递归继承迭代的次数。通过使tag成为非类型参数,确定子类数的参数的值可以唯一地与标记每个子对象类型的参数的值相关。例如,tag = 10对应N = 10,它指的是层次结构中的第10代:

// disambiguated_wrapper.h

struct int_wrapper {
    int num;
};

template < typename base, unsigned int tag >
struct disambiguated_wrapper : base {
    using base::base;
};

// improvised_container.h

#include "disambiguated_wrapper.h"

template <unsigned int N>
struct improvised_container : 
    protected disambiguated_wrapper<int_wrapper, N>, 
    protected improvised_container<N - 1> {

    unsigned int size() const { return N; }

    int& at(const unsigned int index) {
        if (index >= N) throw "out of range";
        else return (index == N - 1) ?
            this->disambiguated_wrapper<int_wrapper, N>::num :
            this->helper(index);
    }
protected:
    int& helper(const unsigned int index) {
        return (index == N - 1) ?
            this->disambiguated_wrapper<int_wrapper, N>::num :
            this->improvised_container<N - 1>::helper(index);
    }
};
#include "specializations.h"

// specializations.h

template <>
struct improvised_container<0> {
    improvised_container() = delete;
}; // ^ prohibits 0-length container

template <>
struct improvised_container<1> : 
    protected disambiguated_wrapper<int_wrapper, 1> {

    unsigned int size() const { return 1; }

    int& at(const unsigned int index) {
        if (index != 0) throw "out of range";
        else return this->disambiguated_wrapper<int_wrapper, 1>::num;
    }
protected:
    int& helper(const unsigned int index) {
        if (index != 0) throw "out of range";
        else return this->disambiguated_wrapper<int_wrapper, 1>::num;
    }
};

// main.cpp

#include "improvised_container.h"
#include <iostream>
int main() {
    improvised_container<10> my_container;
    for (unsigned int i = 0; i < my_container.size(); ++i) {
        my_container.at(i) = i;
        std::cout << my_container.at(i) << ",";
    }   // ^ Output: "0,1,2,3,4,5,6,7,8,9,"
}

元素访问at无法递减index以递归调用自身,因为index不是编译时常量。但是N是。所以,at调用helper,它在(i - 1) th 子对象中递归调用自身的(i - 1) th 版本,递减N直到它等于index – 1,每次调用都会使一个范围更深,最后返回目标范围的元素。它会检查index – 1而非index,因为0 th improvised_container特化的ctor是delete d。 at补偿了一个接一个。

improvised_container使用protected继承来阻止客户端代码访问其基础子对象的atsize方法。子对象的大小小于封闭对象的大小。

这适用于g ++ 4.8。继承构造函数using base::base在VC12中导致错误,但可以省略它,因为元素类型为int