关于继承:死亡钻石和范围解析算子(c ++)

Diamond of death and Scope resolution operator (c++)

我有这个代码(钻石问题):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
using namespace std;

struct Top
{
    void print() { cout <<"Top::print()" << endl; }
};

struct Right : Top
{
    void print() { cout <<"Right::print()" << endl; }
};

struct Left : Top
{
    void print() { cout <<"Left::print()" << endl; }
};

struct Bottom: Right, Left{};

int main()
{
    Bottom b;
    b.Right::Top::print();
}

我想打电话给Top班的print()

当我试图编译它时,我得到了错误:'Top' is an ambiguous base of 'Bottom'在这行:b.Right::Top::print();为什么模棱两可?我明确指出我要从Right而不是从Left得到Top

我不想知道怎么做,是的,它可以通过引用、虚拟继承等来完成。我只想知道为什么b.Right::Top::print();不明确。


Why is it ambiguous? I explicitly specified that I want Top from Right and not from Left.

这是你的意图,但事实并非如此。Right::Top::print()显式地命名要调用的成员函数,即&Top::print。但是它没有指定我们调用该成员函数的b的哪个子对象。您的代码在概念上等价于:

1
2
auto print = &Bottom::Right::Top::print;  // ok
(b.*print)();                             // error

选择print的部分是明确的。从bTop的隐式转换是模糊的。你必须通过做如下的事情来明确地消除你要朝哪个方向走的歧义:

1
static_cast<Right&>(b).Top::print();


作用域解析运算符是左相关的(尽管它不允许使用括号)。

因此,当您想在b中引用A::tell时,id表达式在B::A中引用tell,它只是A而已,这是不明确的。

解决方法是先将其铸造到明确的基础b,然后再铸造到A

语言律师:

[basic.lookup.qual]/1说,

The name of a class or namespace member or enumerator can be referred to after the :: scope resolution operator applied to a nested-name-specifier that denotes its class, namespace, or enumeration.

嵌套名称说明符的相关语法是,

nested-name-specifier:

    type-name ::

    nested-name-specifier identifier ::

因此,第一个嵌套的名称说明符是B::,在其中查找A。那么,B::A是一个嵌套的名称说明符,表示A,在其中查找tell

显然,MSVC接受了这个例子。可能它有一个非标准的扩展,通过这种说明符进行回溯来解决歧义。


事实上,在我在Visual Studio 2019上尝试时,提供代码工作得很好。有两种方法可以解决钻石问题;-使用范围解析运算符-作为虚拟继承基类

执行b.Right::Top::print()调用print函数时应无误。但是仍然有两个从底层类引用的基类(top)对象。

你可以在这里找到更多的细节