友好性和访问级别的继承

3

我有一个测验问题:

#include <iostream>
using namespace std;

struct A
{
    void ohai() { cout  << "ohai" << endl; }
};

struct C : private A {
    friend int main();
};

struct X : C {};
struct Y : private C {};

int main() {    
    C().ohai()  // OK
    X().ohai(); // OK
    Y().ohai(); // Not OK
}

问题在于C类私有继承A类,因此,在C类中,即使是公共成员,在C类中也变为了私有成员。 C类还声明了与主函数的友好关系(这是可以的,现在“main”函数可以调用C类的私有方法)。 然后有X和Y两个类,都从C类派生而来。 X类公开地从C类派生,Y类私有地从C类派生。
在“main”函数中,我创建了一个C类实例并调用了从A类继承而来的“ohai”方法,这很正常,因为主函数是C类的友元。 然后我创建了一个X类实例并调用了“ohai”方法-这出乎意料地奏效了! 但是当我创建一个Y类实例时,这就不起作用了!
我知道继承是不可继承的。因此,这不是友好关系继承,允许“main”在类X的对象上调用“ohai”方法。 类Y的问题证实了这一点,因为只需将继承类型更改为私有即可停止此操作。
我想知道为什么可以在X对象上调用“ohai”。继承不能提升函数的级别(从私有到公共),因此,如果X从C继承,即使是公开继承,所有C中的私有方法也应该在X中保持私有。并且由于友好关系不可继承,“main”方法也不应该能够调用X的私有方法。 Y类私有继承C类,这使得C类的所有成员在X中都是私有的,但是“ohai”方法已经在C类中是私有的(因为C类私有继承A类),因此这不应该改变任何东西,但是与X相比,它却以某种方式发生了变化。
请帮助我理解这个问题。 最好的问候 YotKay
1个回答

1

我不完全确定我的答案是否正确。如果我错了,请纠正我。

我认为这里发生的是,当你调用 X().ohai() 时,在 X 中的 this 指针会被隐式转换为其基类 C,后者授予了 main 友元权限。更确切地说,当编译器在 X 中查找 ohai 时,它既没有在 X 中找到,也没有在直接基类 C 中找到,而是在 A 中找到。友元和其他访问控制机制不会干扰编译器查找成员函数的位置,其影响出现在找到成员之后。一旦编译器在 A 中找到 ohai,就需要将一个 X * 指针(即 this)转换为 A *。 (到这个点上,我很确定我是正确的)。X 是从 C 公开继承的,因此它可以将其转换为 C *。由于 C 给了 main 友好权限,所以它可以继续将其转换为 A *。因此,它能够调用 ohai。(最后一部分不是很精确,但更易于理解)。
例如,尝试以下内容:
int main()
{
    X x;
    Y y;
    static_cast<A *>(&x);
    static_cast<A *>(&y);
}

第一个转换没有问题,而第二个转换在Clang中会给出以下错误:

test.cpp:24:22: error: cannot cast 'Y' to its private base class 'A'
    static_cast<A *>(&y);
                     ^
test.cpp:15:11: note: constrained by private inheritance here
class Y : private C
          ^~~~~~~~~

如果您删除friend声明,第一个转换也会失败,但原因不同。
“友谊不能被继承”是正确的,但实际上与此处发生的事情无关。
如果您删除友元声明,并尝试以下操作:
int main()
{
    X x;
    Y y;
    static_cast<A *>(&x);
    static_cast<C *>(&x);
}

第一个转换将因为访问控制而失败,而第二个转换将成功。

对于主函数的友元关系不仅授予了其访问的私有成员,还包括了它的私有基类。为此,请尝试以下内容并在有或没有友元声明的情况下进行

int main()
{
    C c;
    static_cast<A *>(&c);
}

最后,请尝试以下内容。
struct A {
    void ohai() { std::cout << "ohai" << std::endl; }
};

struct C : private A {
    using A::ohai;
};

struct X : C {
    void foo() { C::ohai(); } // static_cast<C *>(this)->ohai();
    void bar() { A::ohai(); } // static_cast<A *>(this)->ohai();
};

int main()
{
    X x;
    x.foo();
    x.bar();
    x.ohai();
}

函数foo将正常运行,但bar将失败。因为X可以转换为其直接基类C并调用在C中公开的ohai。但是它不能被转换为A并调用A中的ohai

谢谢。也许你是对的。我尝试了你的例子,在 GCC 中,对于示例(1),我在两种情况下都得到了相同的错误(“error:'A'是 'X' 的不可访问基类”),无论是否有友元声明(与 Clang 相反,根据你所写的)。在第二个例子中,就像你所写的那样,第一个转换会失败并出现与上述相同的错误,而第二个转换则正常工作。 - YotKay
@YotKay 只是“可能”,不要对我的答案太肯定。我认为它可以解释发生了什么。但在我说“到目前为止,我非常确定我是正确的”之后,我没有在标准中找到具体的短语。如果给时间,其他更有经验的人可能会给你更好的答案。 - Yan Zhou
@YotKay 访问控制在解析成员函数调用之后进行,而成员函数调用将涉及到对基类的隐式转换,这一点是很清楚的。 - Yan Zhou
有其他的答案吗? - YotKay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接