派生到基础的转换和友谊混乱

Derived-to-base conversion and friendship confusion

来自C++ Primer第5版(D继承自B)

Member functions and friends of classes derived from D may use the derived-tobase conversion if D inherits from B using either public or protected. Such code may not use the conversion if D inherits privately from B.

这有什么原因吗?还是我打算按表面价值来看待它?为什么会这样似乎很明显,但在一个例子中它让我感到困惑:

#include <iostream>
using namespace std;

class Base { 
public: 
    int x = 2;
};

class Derived : protected Base { };

class DerivedAgain : public Derived {
    friend void test();
};

void test() {
    ??? a;
    Base* p = &a;
    cout << p->x;
}

int main(){
    test();
}

我想了解 test() 在派生到基础转换中对成员 x 的可访问性。考虑函数 test()a 类型 ??? 的三种潜在情况。

  1. ???BasexBase 的 public 成员。在这种情况下没有问题。
  2. ???DerivedAgain。在这种情况下,Derived-to-Base 转换是有意义的,因为 test() 可以 friend 访问 DerivedAgain 的所有成员,包括那些间接继承自 Base 的成员。所以使用指针访问 x.
  3. 没有问题
  4. ???Derived。它编译得很好。但为什么?我现在很困惑。 test() 没有对 Derived class 的成员的特殊访问权限,那么为什么 p->x 可以工作,因此 Derived-to-Base 转换是有效的?它是否有效?

的确,如果我把test()的操作改成

void test() {
    Derived a;
    cout << a.x;
}

它没有像我预期的那样编译 - 因为 Derived 对象继承的 x 成员是 protected 的,因此用户不能使用.

如果我用 BaseDerivedAgain 替换 a 的类型,修改后的 test() 可以正常编译,正如我所期望的那样。

我只是对为什么允许二级派生的友元函数 class 使用一级直接到基数的转换感到困惑,如果该友元函数没有特殊访问权限到第一级派生 class 成员。

基本上,受保护的继承很奇怪。它编译的原因是,从 [class.access.base] 到 N4527:

A base class B of N is accessible at R, if
— an invented public member of B would be a public member of N, or
R occurs in a member or friend of class N, and an invented public member of B would be a private or protected member of N, or
R occurs in a member or friend of a class P derived from N, and an invented public member of B would be a private or protected member of P, or
— there exists a class S such that B is a base class of S accessible at R and S is a base class of N accessible at R.

这里的第三个要点是相关的。 R 出现在从 N (Derived),而发明的 B (Base) 的 public 成员将是 P (DerivedAgain) 的受保护成员。

我以前认为接受此代码是一个 gcc 错误 (bug 67493), but now I believe that failing to accept it is a clang bug - although as additionally points out, there is a relevant standard defect (CWG #1873). The wording change there applied only to member access, whereas what's relevant to us here is base access. But perhaps gcc is simply implementing the rule of the standard (acceptance is correct) whereas clang is following with the logical conclusion of this defect report (currently active CWG #472) 而只是不允许它。

再说一次,受保护的继承真的很奇怪。欢迎来到 C++ 的精彩世界。