【问题标题】:Member access rules for friend of derived class, where the naming class is the base class派生类友元的成员访问规则,其中命名类为基类
【发布时间】:2021-01-26 17:10:21
【问题描述】:

除非另有说明,否则以下所有标准参考均指N4861 (March 2020 post-Prague working draft/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
}

因为N 是上面R 的可访问基类(+)

[class.access.base]/5 还提到 [emphasis mine]:

对成员的访问受该成员所在的类的影响 命名。 此命名类是成员名称所在的类 抬头发现。 [ 注意: [...] 如果两个类成员都访问 运算符和限定 ID 用于命名成员(如 p->T​::​m),命名该成员的类是由 限定 ID 的嵌套名称说明符(即 T)。 — 结束说明 ]

和[强调我的]:

成员 m 可在 R 点访问 当在类中命名时 N if

  • [...]
  • /5.3 m 作为成员 N 受保护,并且R 出现在N 类的成员或朋友中,或在派生类P 的成员 来自N,其中m 作为P 的成员是public、private 或 受保护,或
  • /5.4 存在N 的基类B,可在R 访问,当在类B 中命名时,m 可在R 访问。

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

class N {
  protected:
    int m;
};

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

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

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

似乎&p(类型为P*)隐式转换为N*(实现[class.access.base]/6),但我想知道@987654358 的成员m 的规则是什么@(N 是命名类)可通过 R 访问,这是 N 的派生类 P 的朋友。

问题

  • 哪些规则规定#1 格式正确?

如上所述,#1命名类N,但 [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 可以说没有提到 [class.protected]/1 在(非规范)示例中显示的“或P类的朋友”的情况。


(+) 一个可访问的基类

在以下示例中:

class B { };

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

void f()  { /* R */ }

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

N 的基类 B 可通过 R 访问,如果

  • /4.1 [...]
  • /4.2 R 发生在成员或班级朋友 NB 的发明公共成员将是私有成员或受保护成员 P,[...]

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

【问题讨论】:

  • 在我看来 class N{ protected: int m;};class P:private N{ void fun(){ N& rf = *this; rf.m = 1;} }; 其中 rf.m = 1 应该按照 [class.access.base#5.3] 格式正确,因为 R 在 成员函数中 P.

标签: c++ language-lawyer


【解决方案1】:

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

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

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

非静态数据成员或非静态成员函数是其命名的受保护成员时,将应用超出前面条款 [class.access] 中描述的附加访问检查class ([class.access.base])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
}

命名类为 N,非静态成员 mN 的受保护成员,因此条件 1 和 2 为真。

m 作为私人成员可以在P 的朋友中访问,因此条件 3 为真。

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-03-03
    • 1970-01-01
    • 2017-08-07
    • 2020-11-24
    • 2017-09-30
    • 2012-09-16
    • 2018-08-23
    • 1970-01-01
    相关资源
    最近更新 更多