2011-08-26 44 views
13

什么是C++ 98/C++ 03标准和C++ 0x未来标准的确切规则dominance in virtual inheritance虚拟继承中的优势

我并不是要求特定的段落,虽然我也在问(第10节中的某处,我猜)。

我也在问清楚标准的解释,标准的后果。

+0

对于虚拟继承,“统治”意味着什么? –

+1

@尼科尔:C++ 98标准的索引是指虚拟继承和第167页。这就是所有的标准直接说(据我所知,索引是非规范的)。解释优势将超过此评论,所以,只是谷歌它,或[维基百科](http://en.wikipedia.org/wiki/Dominance_%28C%2B%2B%29) - 如果你不知道关于它,无论如何你可能无法回答这个问题。欢呼声, –

+0

@尼科尔:感谢提问,我添加了该问题的链接。 –

回答

25

我认为这是你正在寻找的语言。在C++ 03 ISO规格,在§ 10.2/2,我们有以下:

以下步骤定义的一类范围,C.首先,每个声明为 名称名称查找的结果在类中以及在其每个基类中的子对象被考虑​​。如果A是B的基类子对象,则在一个子对象B中的成员名称f隐藏了子对象A中的成员名称f。任何被隐藏的声明 都不考虑。由 使用声明引入的每个声明均被视为来自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 
          } 
         }; 

在指定的行调用f()如下解决。首先,我们将B::fD::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;和子对象集,这是一组子对象,其中声明了这些成员(可能包括使用声明)。在声明集中,使用声明被它们指定的成员替换,并且类型声明(包括注入类名称)被它们指定的类型替换。 S(F,C)如下计算:

在这方面,C是指其中所述查找发生的范围。换句话说,集合S(f, C)的意思是“当我尝试在类范围C中查找f?时可见的声明是什么?”为了回答这个问题,该规范定义了一个算法来确定这一点。第一个步骤是如下:(§ 10.2/4)

如果C含有名称f的声明,该声明集包含的F IN Ç满足语言的要求构造中声明每个声明查找发生。 [...]如果生成的声明集不为空,则子对象集合本身包含C ,并且计算已完成。

换句话说,如果类本身有f一些所谓的在它声明,那么宣言中只是一套东西命名为f在类中定义(或用using申报进口)。但是,如果我们找不到任何名为f的东西,或者如果所有名为f的东西都是错误的类型(例如,我们需要类型时的函数声明),那么我们继续下一步:(§ 10.2/5 )

否则(即,C不包含f的声明或结果声明集为空),S(f,C)初始为空。如果C具有基类,则计算每个直接基类子对象f中的f的查找集合,并依次将每个这样的查找集合S(f,B 和)合并到S(f,C)中。

换句话说,我们将查看基类,计算这些基类中的名称可能引用的内容,然后将所有内容合并到一起。下一步将指定您合并的实际方式。这真的很棘手(它有三个部分),所以这是一击一吹。这是原来的措辞:(§ 10。2/6)

以下步骤定义合并查找集S(f的结果,B )到中间S(F,C):

  • 如果每个的S(f,B i)的子对象成员是S(f,C)的子对象的至少一个子对象 的基类子对象,或者如果S(f,B 和)为空,则S f,C)不变并且合并完成。相反,如果S(f,C)的每个子对象成员都是S(f,B i)的至少一个子对象成员的基类子对象,或者S(f,C)为空,新的S(f,C)是S(f,Bi)的副本。

  • 否则,如果声明集S的(F,B )和S(F,C)不同,合并是不明确的:新 S(F,C)是具有设定的查找无效的声明集合和子对象集合的联合。在随后的 合并中,无效声明集被认为与其他任何声明集都不相同。否则,新的S(f,C)是具有共享声明集合和子对象集合的联合的查找集合。

好,让这一块除了一次一个。这里的第一条规则有两个部分。第一部分说如果你试图将一组空的声明合并到整个集合中,你根本就不会做任何事情。这就说得通了。它还说,如果你试图合并一些已经合并到目前为止基础类的东西,那么你什么都不做。这很重要,因为这意味着如果你隐藏了一些东西,你不想通过重新合并来重新引入它。

第一条规则的第二部分说如果你正在合并的东西in从已经合并到目前为止的所有内容中派生而来,您将您到目前为止计算的集合替换为您为派生类型计算的数据。这基本上说,如果你已经合并了许多看起来没有连接的类,然后在统一所有这些类的类中合并,那么抛出旧数据并使用已经计算出的派生类型的数据。

现在我们来看第二条规则。这让我花了一段时间才明白,所以我可能有这个错误,但我认为这是说如果你在两个不同的基类中进行查找并取回不同的东西,那么这个名称是不明确的,你应该报告某事是如果你想在这一点上试图查看这个名字,那就错了。

最后一条规则说如果我们不在这两种特殊情况之一,没有什么是错的,你应该把它们合并起来。

P ......那很难!让我们来看看当我们追查上面的钻石继承时会发生什么。我们要查找名称f,从A开始。由于A没有定义f,因此我们计算从B开始查找f和从C开始的f的值。让我们看看发生了什么。当计算fB中的含义时,我们看到定义了B::f,所以我们停止查找。在B仰视f值是集(B::fB}。要查找什么f意味着C,我们期待在C,并认为它并没有定义f,所以我们再次递归从D查找值。在D中进行查找得到{D::f,D},并且当我们将所有内容合并在一起时,我们发现应用规则1的后半部分(因为子对象集合中的每个对象都是基地D),所以最终C的值由{D::f,D}给出。

F最后,我们需要合并BC的值。这尝试合并{D::f,D}和{B::fB}。这是它获得乐趣的地方。假设我们按照这个顺序合并。合并{,D}和空集产生{D::f,D}。当我们现在合并在{B::fB}中时,那么因为DB的一个基数,所以在规则一的后半部分,我们覆盖旧的集合,并以{B::fB}结束。因此,f的查找是Bf的版本。

如果,另一方面,我们以相反的顺序合并,我们先从{B::fB}并尝试合并在{D::fD}。但由于DB的基数,我们只是忽略它而留下{B::fB}。我们已经达到了相同的结果。很酷,是吧?我很惊讶,这工作得很好!

所以你有它 - 旧的规则是真的(简单)直接,新规则是不可能的复杂,但无论如何设法解决。

希望这会有所帮助!

+0

@Alf P. Steinbach-我刚刚用C++ 11的规则更新了这个,而且这个人很笨。希望这有助于解释事情(?)! – templatetypedef

+0

+1用于涉水。我想我现在理解了C++ 98的一部分。好。令人惊讶的简单。然而,当我开始阅读C++ 11部分时,我的目光掠过......干杯, –

+2

+1只是为了努力必须进入这个答案o_O – ildjarn