Che*_*Alf 13 c++ inheritance virtual-inheritance c++11
什么是C++ 98/C++ 03标准'和C++ 0x未来标准的虚拟继承优势的确切规则?
我不是只要求具体的段落,虽然我也要求它(在第10节的某个地方,我猜).
我还要问,标准清楚地说明了标准的后果.
tem*_*def 25
我认为这是你正在寻找的语言.在C++ 03 ISO规范中,在§10.2/ 2中,我们有以下内容:
以下步骤在类范围C中定义名称查找的结果.首先,考虑类中及其每个基类子对象中的名称的每个声明.如果A是B的基类子对象,则一个子对象B中的成员名称f隐藏子对象A中的成员名称f.任何如此隐藏的声明都将被排除在考虑范围之外.由using声明引入的每个声明都被认为来自C的每个子对象,该子对象的类型包含using声明指定的声明.如果生成的声明集不是来自相同类型的子对象,或者集合具有非静态成员并且包括来自不同子对象的成员,则存在歧义并且程序格式错误.否则该集合是查找的结果.
在较高的层次上,这意味着当您尝试查找名称时,它会查找所有基类和类本身以查找该名称的声明.然后逐个类,如果其中一个基础对象具有该名称的东西,它会隐藏在该对象的任何基类中引入的所有名称.
这一行的一个重要细节是:
任何如此隐藏的声明都不予考虑.
重要的是,这表示如果某些东西被任何东西隐藏,它被认为是隐藏和删除的.所以,例如,如果我这样做:
class D {
public:
void f();
}
class B: virtual public D { class C: virtual public D {
public: public:
void f(); /* empty */
}; };
class A: public B, public C {
public:
void doSomething() {
f(); // <--- This line
}
};
Run Code Online (Sandbox Code Playgroud)
在指示的行上,调用f()解析如下.首先,我们添加B::f和D::f设定的名称可以考虑. D::f因为D没有基类,所以不会隐藏任何内容.但是,B::f确实隐藏了D::f,所以即使D::f可以在A没有看到的情况下到达B::f,它也被认为是隐藏的并从可以命名的对象集中移除f.既然只剩下B::f,就是那个叫做的人.ISO规范提到(§10.2/ 7)
当使用虚拟基类时,可以沿着通过子对象点阵的路径到达隐藏声明,该子对象点阵不通过隐藏声明.这不是含糊不清的.[...]
我认为这是因为上述规则.
在C++ 11中(根据草案规范N3242),规则的拼写比以前更明确,并且给出了实际算法来计算名称的含义.这是语言,一步一步.
我们从§10.2/ 3开始:
C中f的查找集,称为S(f,C),由两个组件集组成:声明集,一组名为f的成员; 和子对象集,一组子对象,其中发现了这些成员的声明(可能包括使用声明).在声明集中,using声明由它们指定的成员替换,类型声明(包括inject-class-names)被它们指定的类型替换.S(f,C)计算如下:
在此上下文中,C指的是查找发生的范围.换句话说,该集合S(f, C)意味着"当我尝试f在类范围内查找时,可见的声明是什么C?" 为了回答这个问题,规范定义了一种算法来确定这一点.第一步如下:(§10.2/ 4)
如果C包含名称f的声明,则声明集包含在C中声明的f的每个声明,该声明满足查找发生的语言构造的要求.[...]如果生成的声明集不为空,则子对象集包含C本身,并且计算完成.
换句话说,如果类本身具有f在其中声明的内容,则声明集只是f该类中定义的名称集合(或使用using声明导入).但是,如果我们找不到任何命名f,或者如果命名的所有内容f都是错误的排序(例如,当我们想要一个类型时的函数声明),那么我们继续下一步:(§10.2/ 5)
否则(即,C不包含f的声明或结果声明集为空),S(f,C)最初为空.如果C具有基类,则在每个直接基类子对象B i中计算f的查找集,并将每个这样的查找集S(f,B i)依次合并为S(f,C).
换句话说,我们将查看基类,计算名称在这些基类中可以引用的内容,然后将所有内容合并在一起.您执行合并的实际方式将在下一步中指定.这真的很棘手(它有三个部分),所以这是一个一个接一个的打击.这是最初的措辞:(§10.2/ 6)
以下步骤定义将查找集S(f,B i)合并到中间S(f,C)的结果:
如果S(f,B i)的每个子对象成员是S(f,C)的至少一个子对象成员的基类子对象,或者如果S(f,B i)为空,则S(f) ,C)没有变化,合并完成.相反,如果S(f,C)的每个子对象成员是S(f,B i)的至少一个子对象成员的基类子对象,或者如果S(f,C)为空,则新的S(f,C)是S(f,Bi)的副本.
否则,如果S(f,B i)和S(f,C)的声明集不同,则合并是不明确的:新的S(f,C)是具有无效声明集的查找集和子对象集.在后续合并中,无效的声明集被认为与其他任何不同.
否则,新的S(f,C)是具有共享声明集和子对象集的并集的查找集.
好吧,让我们分开一个.这里的第一条规则有两部分.第一部分说如果你试图将一组空的声明合并到整个集合中,你根本不做任何事情.那讲得通.它还说,如果你试图合并某个东西,那是迄今为止已合并的所有内容的基类,那么你根本就不做任何事情.这很重要,因为这意味着如果你隐藏了某些东西,你不想通过合并它来不小心重新引入它.
第一条规则的第二部分说明,如果你要合并的东西是从迄今为止合并的所有内容派生出来的,那么你用你为派生类型计算的数据替换你到目前为止所计算的集合. .这基本上说如果你把许多似乎没有连接的类合并在一起然后合并到一个统一所有类的类中,抛弃旧数据并只使用你已经计算过的派生类型的数据.
现在让我们来看看第二条规则.这花了我一段时间来理解,所以我可能有这个错误,但我认为它说如果你在两个不同的基类中进行查找并找回不同的东西,那么名称是模糊的,你应该报告一些东西是如果您此时尝试查找名称,则会出错.
最后一条规则说如果我们不处于这两种特殊情况中的任何一种情况,那么没有什么是错的,你应该将它们结合起来.
Phew ......这太难了!让我们看看当我们为上面的钻石继承追踪它时会发生什么.我们想f从头开始查找名称A.由于A没有定义f,我们计算的仰视值f首发B并f在开始C.让我们看看发生了什么.当计算出什么f意思的价值时B,我们看到这B::f是定义的,所以我们停止寻找.仰视值f中B是一组(B::f,B}.要查找什么f意思的C,我们看在C看,它并没有定义f,所以我们再次递归查找从值D,否则在查找D生成{ D::f,D},当我们将所有内容合并在一起时,我们发现规则1的后半部分适用(因为它实际上是子对象集中的每个对象都是其基础D),因此最终值C由{ D::f,D} 给出.
最后,我们需要的值合并在一起B和C.这会尝试合并{ D::f,D}和{ B::f,B}.这是它变得有趣的地方.我们假设我们按此顺序合并.合并{ D::f,D}和空集会产生{ D::f,D}.当我们现在合并{ B::f,B}时,因为D是B规则一的后半部分,我们覆盖旧的集合,最后得到{ B::f,B}.因此,查找f是fin 的版本B.
另一方面,如果我们以相反的顺序合并,我们从{ B::f,B} 开始并尝试合并{ D::f,D}.但由于D它是基础B,我们只是忽略它,留下{ B::f,B}.我们得出了同样的结果.很酷,对吧?我很惊讶这很好用!
所以,你有它 - 旧的规则真的(ish)直截了当,新的规则是不可能复杂的,但无论如何设法成功.
希望这可以帮助!