我将以下内容称为“多重继承”:
我想知道它是否存在以及如何明确地访问嵌入的子对象。
1。)[ Professional C ++ ,2 nd ed。] †声明可编辑的程序不能有一个类直接继承其直接父级和父级的父级。这是真的吗?
鉴于扩展GrandParent
的{{1}}和Parent
,VC12和g ++允许GrandParent
直接从GrandChild
和{{1}继承}。在VC12和g ++中,可以按如下方式定义这些类:
Parent
声明GrandParent
数据成员。 GrandParent
除了继承int num
' s Parent
之外,还声明了自己的num
。 GrandParent
除了继承num
和GrandChild
' 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.打印。
答案 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
。
如果类似菱形的问题被推广为包含更多代:
base
和(i - 1) th ,base
和(i - 2) th ,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
继承来阻止客户端代码访问其基础子对象的at
和size
方法。子对象的大小小于封闭对象的大小。
这适用于g ++ 4.8。继承构造函数using base::base
在VC12中导致错误,但可以省略它,因为元素类型为int
。