派生类友元成员访问规则,其中命名类为基类

dfr*_*fri 6 c++ language-lawyer

除非另有说明,否则以下所有标准参考均指N4861(2020 年 3 月布拉格后工作草案/C++20 DIS)


背景

根据[class.access.base]/5

如果基类是可访问的,则可以将指向派生类的指针隐式转换为指向该基类的指针 [...]。

这意味着以下示例是格式良好的:

class N {};

class P : private N {
    friend void f();
};

void f()  { 
    P p{};
    N* n = &p; // R: OK as per [class.access.base]/5
}
Run Code Online (Sandbox Code Playgroud)

asNR上面(+)处的可访问基类。

[class.access.base]/5 还提到[强调我的]:

对成员的访问受命名该成员的类的影响。此命名类是在其中查找并找到成员名称的类。[注: [...]如果两个类成员访问运算符和一个合格-ID被用于命名构件(如在 p->T?::?m),类命名构件是由合格的嵌套名称说明符所表示的类-id(即,T)。—尾注 ]

和[强调我的]:

一个成员在类中命名时m可以访问,如果R N

  • [...]
  • /5.3m作为的成员 N 是受保护的,并且R出现在类的成员或朋友中N,或P派生自的类的成员中N,其中m作为 的成员P是公共的、私有的或受保护的,或
  • /5.4存在一个基类BN也就是在访问R,并m在访问R时,在课堂命名B

考虑到这一点,请考虑以下示例:

class N {
  protected:
    int m;
};

class P : private N {
    friend void f();
};

void f()  {
    P p{};
    (&p)->N::m = 42;  // R: #1
}
Run Code Online (Sandbox Code Playgroud)

其中按照上述命名类#1IS N。该示例被 Clang 和 GCC 接受,适用于各种编译器版本和标准,这意味着它可以说是格式良好的。

那就好像&p(这是类型P*)隐式转换N*(履行[class.access.base / 6),但我用什么样的规则成员想知道mNN被命名类)可访问在R其是 的派生类P的朋友N

  • 什么规则(S)管理#1良好格式?

按照上述,在命名类#1N,但[class.access.base] /5.3不应作为应用R在朋友类的P衍生自N(/5.3只提到在一个构件类的P)。[class.access.base]/5.4不应适用,因为命名类是N类层次结构中的顶级类。

我们可能会注意到[class.protected]/1提到了上面的例子,作为段落的非规范示例块的一部分。但是, [class.protected]/1 的整体描述为

额外的访问检查 [...]

可以说意味着 [class.access.base] 仍然需要申请;似乎 [class.access.base]/5.3 似乎没有提到P[class.protected]/1 在(非规范性)示例中显示的“或类的朋友”的情况。


(+) 一个可访问的基类

在以下示例中:

class B { };

class N : B {
    friend void f();
};

void f()  { /* R */ }
Run Code Online (Sandbox Code Playgroud)

根据[class.access.base]/4,特别是[class.access.base]/4.2 [强调我的]:

基类BN是访问R,如果

  • /4.1 [...]
  • /4.2R 出现在类的成员或朋友中 N,并且发明的公共成员B将是 的私有或受保护成员P,[...]

B可在R,即在 的朋友f中访问N

jac*_*k X 2

实际上,当指定成员是命名类的受保护成员(R 出现在派生类的成员或友元处)时,[class.protected#1] 部分是 [class.access.base#5] 的附加子句。
根据class.access.base#1, 的非静态 protected 成员N可以作为派生类的私有成员进行访问P。如果命名类是,我们可以按照class.access.base#5.2m访问友元中的成员PP

m 作为 N 的成员是私有的,并且 R 出现在 N 类的成员或友元中,或者

回到[class.protected#1],我们应该看一下以下规则:

当非静态数据成员或非静态成员函数其命名类([class.access.base])的受保护成员时,将应用超出条款 [class.access] 中先前描述的附加访问检查115 如所述早些时候,授予对受保护成员的访问权限,因为引用发生在某个 C 类的友元或成员中。

换句话说,该规则规定,只有满足这些条件,才会应用附加规则。那是:

  1. 该成员首先应该是非静态成员(数据或函数)
  2. 该成员应该是命名类的受保护成员。

为了使附加规则适用,还应满足以下条件

  1. 如前所述,授予对受保护成员的访问权限是因为引用发生在某个类 C 的友元或成员中。
  2. 如果访问是为了形成指向成员 ([expr.unary.op]) 的指针,则嵌套名称说明符应表示 C 或从 C 派生的类。
  3. 所有其他访问都涉及(可能是隐式的)对象表达式。在这种情况下,对象表达式的类应为 C 或从 C 派生的类。

条件3可能有些混乱,但是它并没有说C一定是命名类。它只是说可以在 的会员或朋友中访问该会员C

理清了这些条件之后,我们就可以看一下例子了

void f()  {
    P p{};
    (&p)->N::m = 42;  // R: #1
}
Run Code Online (Sandbox Code Playgroud)

命名类是 N 并且非静态成员m是 protected of N,因此条件 1 和 2 为真。

m由于 的 好友可以访问私有成员P,因此条件 3 为真。

(&p)->N::m是一个类成员访问表达式,其对象表达式的类型为 P,因此条件 5 为真。因此,当这样的表达式出现在 中时,它是格式良好的f

如果将非静态成员更改m为静态成员,则条件 1 为 false,这意味着附加规则将不适用于表达式。

简单来说,如果满足这些条件,[class.protected#1] 会为 [class.access.base#5.2] 的后一个项目符号添加一个额外的选项(即朋友)。此外,它还限制这些可以根据 [class.access.base#5.2] 的后一个项目符号访问的成员,以在满足这些条件时将其转变为不可访问的成员。