好吧,我已经被这个邪恶的东西困扰了一个晚上。无休止的讨论和第 11.4 条的模棱两可(由 Yam marcovic 引用)
§ 11.4 受保护的成员访问
[1] 当非静态数据成员或非静态成员函数是其命名类的受保护成员时,将应用第 11 条中所述之外的附加访问检查...如前所述,访问protected 成员被授予,因为引用发生在某个 C 类的朋友或成员中。
把我烧坏了。我决定求助于 gcc 源代码(在我的例子中是 gcc 4.9.2)来检查那些 gcc 人如何理解第 11.4 条,以及 C++ 标准想要做什么检查以及应该如何进行这些检查。
在 gcc/cp/search.c:
/* Returns nonzero if it is OK to access DECL through an object
indicated by BINFO in the context of DERIVED. */
static int protected_accessible_p (tree decl, tree derived, tree binfo)
{
access_kind access;
/* We're checking this clause from [class.access.base]
m as a member of N is protected, and the reference occurs in a
member or friend of class N, or in a member or friend of a
class P derived from N, where m as a member of P is public, private
or protected.
Here DERIVED is a possible P, DECL is m and BINFO_TYPE (binfo) is N. */
/* If DERIVED isn't derived from N, then it can't be a P. */
if (!DERIVED_FROM_P (BINFO_TYPE (binfo), derived))
return 0;
access = access_in_type (derived, decl);
/* If m is inaccessible in DERIVED, then it's not a P. */
if (access == ak_none)
return 0;
/* [class.protected]
When a friend or a member function of a derived class references
a protected nonstatic member of a base class, an access check
applies in addition to those described earlier in clause
_class.access_) Except when forming a pointer to member
(_expr.unary.op_), the access must be through a pointer to,
reference to, or object of the derived class itself (or any class
derived from that class) (_expr.ref_). If the access is to form
a pointer to member, the nested-name-specifier shall name the
derived class (or any class derived from that class). */
if (DECL_NONSTATIC_MEMBER_P (decl))
{
/* We can tell through what the reference is occurring by
chasing BINFO up to the root. */
tree t = binfo;
while (BINFO_INHERITANCE_CHAIN (t))
t = BINFO_INHERITANCE_CHAIN (t);
if (!DERIVED_FROM_P (derived, BINFO_TYPE (t)))
return 0;
}
return 1;
}
最有趣的部分是:
if (DECL_NONSTATIC_MEMBER_P (decl))
{
/* We can tell through what the reference is occurring by
chasing BINFO up to the root. */
tree t = binfo;
while (BINFO_INHERITANCE_CHAIN (t))
t = BINFO_INHERITANCE_CHAIN (t);
if (!DERIVED_FROM_P (derived, BINFO_TYPE (t)))
return 0;
}
1) 在代码中派生的是上下文,在我的例子中是派生类;
2)代码中的binfo代表非静态受保护成员为access的实例,在我的例子中是base_,Derived的受保护数据成员Base实例;
3) 代码中的decl代表base_.b_。
gcc 在翻译我的相关代码时所做的是:
1) 检查 base__.b_ 是否为非静态受保护成员?是的,当然,所以输入 if;
2) 爬上base_的继承树;
3) 找出实际类型 base_ 是什么;当然是Base
4) 检查 3) 中作为 Base 的结果是否派生自 Derived。当然,这是负面的。然后返回 0 - 拒绝访问。
显然,根据 gcc 的实现,C++ 标准要求的“附加检查”是通过其访问受保护成员的实例的类型检查。虽然 C++ 标准没有明确提到应该做什么样的检查,但我认为 gcc 的检查是最明智和合理的——它可能是 C++ 标准想要的那种检查。然后问题真的归结为标准要求像这样进行额外检查的理由。它有效地使标准自相矛盾。摆脱那个有趣的部分(在我看来,C++ 标准是故意要求不一致的),代码应该可以完美运行。特别是不会出现兄弟问题,因为它会被语句过滤:
if (!DERIVED_FROM_P(BINFO_TYPE(t), derived))
return 0;
关于 Peter 和他分享的帖子(由 Eric Lippert 撰写)提到的保护类型(protected 不仅仅适用于类,而是适用于类和实例),我个人完全同意这一点。不幸的是,通过查看 C++ 标准的措辞,它没有;如果我们接受 gcc 实现是对标准的准确解释,那么 C++ 标准真正要求的是,受保护的成员可以通过其命名类或从命名类派生的任何东西访问;但是,当通过对象访问受保护成员时,请确保所有者对象的类型与调用上下文的类型相同。看起来标准只是想对我原来问题中的澄清点 1 做一个例外。
最后但同样重要的是,我要感谢 Yam marcovic 指出第 11.4 条。你就是那个人,虽然你的解释不太正确——上下文不一定是 Base,它可以是 Base 或任何从 Base 派生的东西。问题在于访问非静态受保护成员的实例的类型检查。