虚拟继承中的主导地位
C++98/C++03 标准和 C++0x 未来标准的具体规则是什么 虚拟继承中的主导地位?
我不只是要求特定的段落,尽管我也要求特定的段落(我猜是在第 10 节的某个地方)。
我还想问标准语的后果,标准语解释得很清楚。
What are the C++98/C++03 standards' and the C++0x future standard's exact rules for dominance in virtual inheritance?
I'm not asking for just the specific paragraphs, although I'm asking also for that (somewhere in section 10, I'd guess).
I'm asking also for the consequences of the standardese, the standardese explained, clearly.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我认为这就是您正在寻找的语言。在 C++03 ISO 规范的 §10.2/2 中,我们有以下内容:
从较高的层次来看,这意味着当您尝试查找名称时,它会在所有基类和类本身中查找以查找该名称的声明。然后,您逐个类,如果其中一个基对象具有具有该名称的内容,它将隐藏该对象的任何基类中引入的所有名称。
这里的一个重要细节是这一行:
重要的是,这表示如果某物被任何东西隐藏,则它被视为隐藏并删除。因此,例如,如果我这样做:
在指定的行上,对
f()
的调用将按如下方式解析。首先,我们将B::f
和D::f
添加到可以考虑的名称集中。D::f
不会隐藏任何内容,因为D
没有基类。但是,B::f
确实隐藏了D::f
,因此即使可以从A< 访问
D::f
/code> 没有看到B::f
,它被视为隐藏并从可以命名为f
的对象集中删除。由于只剩下B::f
,因此这就是被调用的那个。 ISO 规范提到 (§10.2/7)我认为这是因为上述规则。
在 C++11 中(根据草案规范 N3242),规则的拼写比以前更加明确,并且给出了实际的算法来计算名称的含义。这是语言,一步一步。
我们从 §10.2/3 开始:
在本上下文中,
C
指的是查找发生的范围。换句话说,集合S(f, C)
的意思是“当我尝试在类作用域Cf
时,可见的声明是什么”代码>?”为了回答这个问题,规范定义了一个算法来确定这个问题。第一步如下:(§10.2/4)换句话说,如果类本身声明了名为
f
的东西,那么声明集就是在该类中定义的名为f
的东西的集合(或使用using
声明)。但是,如果我们找不到名为f
的任何内容,或者名为f
的所有内容的排序错误(例如,当我们需要类型时,却是函数声明),然后我们继续下一步:(§10.2/5)换句话说,我们将查看基类,计算名称在这些基类中可以引用的内容,然后将所有内容合并在一起。执行合并的实际方式在下一步中指定。这确实很棘手(它分为三个部分),所以这里是逐一介绍的。原文如下:(§10.2/6)
好吧,让我们一次一个地把它拆开。这里的第一条规则有两个部分。第一部分说,如果您尝试将一组空声明合并到整个集合中,则您根本不需要执行任何操作。这是有道理的。它还说,如果您尝试合并迄今为止已合并的所有内容的基类中的某些内容,那么您根本不需要执行任何操作。这很重要,因为这意味着如果您隐藏了某些内容,您不想通过将其合并回来而意外地重新引入它。
第一条规则的第二部分表示,如果您要合并的内容源自到目前为止已合并的所有内容,您都可以用为派生类型计算的数据替换到目前为止计算的集合。这本质上是说,如果您将许多看似互不相关的类合并在一起,然后合并到一个统一所有这些类的类中,则丢弃旧数据并仅使用您已经计算过的派生类型的数据。
现在让我们来看第二条规则。这花了我一段时间才理解,所以我可能有这个错误,但我认为它是说,如果你在两个不同的基类中进行查找并返回不同的东西,那么名称是不明确的,你应该报告某些东西是如果您此时尝试查找该名称,则错误。
最后一条规则说,如果我们不属于这两种特殊情况,则没有任何问题,您应该将它们结合起来。
唷……这太难了!让我们看看当我们追踪上面的钻石继承时会发生什么。我们想要查找以
A
开头的名称f
。由于A
没有定义f
,因此我们计算从B
和开始查找
从f
的值>fC
开始。让我们看看会发生什么。当计算B
中f
的含义时,我们看到定义了B::f
,因此我们停止查找。在B
中查找f
的值是集合(B::f
,B
}。了解f
在C
中的含义,我们查看C
,发现它没有定义f
,所以我们再次从D
中递归查找值。D
产生 {D::f
,D
},当我们将所有内容合并在一起时,我们发现规则 1 的后半部分适用(因为子对象集中的每个对象都是D
的基数,这是毫无疑问的事实),因此C
的最终值由 {D::f
D::f 给出代码>,<代码>D}。需要将
B
和C
的值合并在一起,这会尝试合并 {D::f
,D
}。和 {B::f
,B
} ,这就是有趣的地方。假设我们按这个顺序合并。 code>,D
} 和空集产生{D::f
,D
}。当我们现在合并到 {B::f
,B
} 时。 ,然后因为D
是B
的基数,根据规则一的后半部分,我们覆盖旧的集合并最终得到 {B::f< /代码>,<代码>B}。因此,
f
的查找是B
中f
的版本。另一方面,如果我们以相反的顺序合并,我们从 {
B::f
,B
} 开始,并尝试在 {D:: f
,D
}。但由于D
是B
的基数,因此我们忽略它,留下 {B::f
,B
}。我们得到了相同的结果。很酷吧?我很惊讶这效果这么好!现在你明白了——旧规则确实很简单,而新规则则复杂得难以置信,但无论如何都能解决。
希望这有帮助!
I think that this is the language you're looking for. In the C++03 ISO spec, in §10.2/2, we have the following:
At a high level, this means that when you try looking up a name, it looks in all of the base classes and the class itself to find declarations for that name. You then go class-by-class and if one of those base objects has something with that name, it hides all of the names introduced in any of that object's base classes.
An important detail here is this line:
Importantly, this says that if something is hidden by anything, it's considered hidden and removed. So, for example, if I do this:
On the indicated line, the call to
f()
is resolved as follows. First, we addB::f
andD::f
to the set of names that could be considered.D::f
doesn't hide anything becauseD
has no base classes. However,B::f
does hideD::f
, so even thoughD::f
can be reached fromA
without seeingB::f
, it's considered hidden and removed from the set of objects that could be namedf
. Since onlyB::f
remains, that's the one that's called. The ISO spec mentions (§10.2/7) thatI think that this is because of the above rule.
In C++11 (according to draft spec N3242), the rules are spelled out much more explicitly than before and an actual algorithm is given to compute what name is meant. Here is the language, step-by-step.
We begin with §10.2/3:
In this context,
C
refers to the scope in which the lookup occurs. In otherwords, the setS(f, C)
means "what are the declarations that are visible when I try to look upf
in class scopeC
?" To answer this, the spec defines an algorithm to determine this. The first step is as follows: (§10.2/4)In other words, if the class itself has something called
f
declared in it, then the declaration set is just the set of things namedf
defined in that class (or imported with ausing
declaration). But, if we can't find anything namedf
, or if everything namedf
is of the wrong sort (for example, a function declaration when we wanted a type), then we go on to the next step: (§10.2/5)In other words, we're going to look at the base classes, compute what the name could refer to in those base classes, then merge everything together. The actual way that you do the merge is specified in the next step. This is really tricky (it has three parts to it), so here's the blow-by-blow. Here's the original wording: (§10.2/6)
Okay, let's piece this apart one at a time. The first rule here has two parts. The first part says that if you're trying to merge an empty set of declarations into the overall set, you don't do anything at all. That makes sense. It also says that if you're trying to merge something in that's a base class of everything already merged so far, then you don't do anything at all. This is important, because it means that if you've hidden something, you don't want to accidentally reintroduce it by merging it back in.
The second part of the first rule says that if the thing you're merging in is derived from everything that's been merged so far, you replace the set you've computed up to this point with the data you've computed for the derived type. This essentially says that if you've merged together a lot of classes that seem unconnected and then merge in a class that unifies all of them, throw out the old data and just use the data for that derived type, which you've already computed.
Now let's go to that second rule. This took me a while to understand, so I may have this wrong, but I think that it's saying that if you conduct the lookup in two different base classes and get back different things, then the name is ambiguous and you should report that something is wrong if you were to try to look up the name at this point.
The last rule says that if we aren't in either of these special cases, nothing is wrong and you should just combine them.
Phew... that was tough! Let's see what happens when we trace this out for the diamond inheritance above. We want to look up the name
f
starting inA
. SinceA
doesn't definef
, we compute the values of looking upf
starting inB
andf
starting inC
. Let's see what happens. When computing the value of whatf
means inB
, we see thatB::f
is defined, and so we stop looking. The value of looking upf
inB
is the set (B::f
,B
}. To look up whatf
means inC
, we look inC
and see that it does not definef
, so we again recursively look up the value fromD
. Doing the lookup inD
yields {D::f
,D
}, and when we merge everything together we find that the second half of rule 1 applies (since it's vacuously true that every object in the subobject set is a base ofD
), so the final value forC
is given by {D::f
,D
}.Finally, we need to merge together the values for
B
andC
. This tries merging {D::f
,D
} and {B::f
,B
}. This is where it gets fun. Let's suppose we merge in this order. Merging {D::f
,D
} and the empty set produces {D::f
,D
}. When we now merge in {B::f
,B
}, then becauseD
is a base ofB
, by the second half of rule one, we override our old set and end up with {B::f
,B
}. Consequently, the lookup off
is the version off
inB
.If, on the other hand, we merge in the opposite order, we start with {
B::f
,B
} and try merging in {D::f
,D
}. But sinceD
is a base ofB
, we just ignore it, leaving {B::f
,B
}. We've arrived at the same result. Pretty cool, huh? I'm amazed that this works out so well!So there you have it - the old rules are really (ish) straightforward, and the new rules are impossibly complex but somehow manage to work out anyway.
Hope this helps!