14

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

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

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

1.) [ Professional C++,第 2版] †</ sup > 声明可编译程序不能有一个类直接继承其直接父类和所述父类的父类。这是真的吗?

给定一个扩展的GrandParentand ,VC12 和 g++ 允许 a直接继承自两个and 。在 VC12 和 g++ 中,可以按如下方式定义这些类:ParentGrandParentGrandChildParentGrandParent

GrandParent声明一个int num数据成员。除了继承's之外,还Parent声明了自己的. 除了继承's 和's之外,还声明了自己的。numGrandParentnumGrandChildnumParentGrandParentnum

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

#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
}

2.) 为什么 (a)gc.Parent::GrandParent::num在 g++ 中是模棱两可的,而 (b)gc.Parent::num不是?(a) 唯一地描述了它在继承树上的位置。gc只有 1Parent个子对象,它只有 1GrandParent个子对象,它只有 1 个num。对于 (b),gc有一个Parent,它有自己的num,但也GrandParent有另一个的子对象num

3.) 对于gc.GrandParent::num,似乎 VC12gcGrandParent后者的num. 我猜它是明确的原因是它是一个由 限定的名称查找,因此在 ' 范围内首先查找gc右侧的实体,并且最直接到' 的范围是直接继承的,而不是通过间接继承一个。我错了吗?.gcGrandParentgcParent

4.) 为什么gc.GrandParent::num对 g++ 不明确 gc.Parent::num?如果一个是模棱两可的,那么两者不应该同样模棱两可吗?对于先验,gc有两个GrandParents;对于后者,Parent有 2num秒。


†</sup>Gregoire, Marc R. 等人。专业 C++,第 2。印第安纳波利斯:威利出版社,2011 年。241. 打印。

4

2 回答 2

3

对此的常用术语是菱形图案(或菱形问题)。

这本身并不是一个错误,但正如这里的评论中所指出的,任何尝试访问在层次结构中其他地方重复的直接基础都将导致模棱两可的错误。

一种解决方法是使基础间接。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::用来消除成员访问的歧义。

于 2013-12-20T03:37:02.577 回答
1

我正在添加已接受的答案。base它指出,如果派生类也间接继承,则派生类不能访问直接类base。它的解决方案base通过使用第二个类型参数为tag. 如果base派生类使用唯一的tag. 下面的示例将使用非类型tag

如果类金刚石问题被概括为包含更多代,形式为:

  • i th类继承自baseand (i – 1) th ,
  • (i – 1) th继承自baseand (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"

// 专业化.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调用,它在第 (i – 1)个子helper对象中递归调用自身的第 (i – 1)版本,递减直到等于,每次调用将范围更深,最后返回目标范围的元素。它检查and not ,因为第 0专业化的 ctor 是d。一个接一个地补偿。Nindex – 1index – 1index improvised_containerdeleteat

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

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

于 2013-12-21T02:50:08.397 回答