深入了解C++受到保护

Getting to the bottom of C++ protected

本文关键字:保护 C++ 了解      更新时间:2023-10-16

2011 C++标准在第 11.4 节中指出

超出第 11 条前面描述的额外访问检查 在非静态数据成员或非静态成员函数时应用 是其命名类 ( 11.2 ) 115 如前所述,授予对受保护成员的访问权限是因为引用发生在某些朋友或成员中 C类 .如果访问是形成指向成员 ( 5.3.1 ),嵌套名称说明符应表示C或从C派生的类。所有其他访问都涉及(可能是隐式的)对象 表达式 ( 5.2.5 )。 在这种情况下,对象表达式的类应为 C 或从 C 派生的类。

(在旧标准中,类似的措辞在第11.5节中。

该规则限制了经常重复的想法,即"B的受保护成员可以被任何B或B的派生类访问"。然而,解释规则是困难的,正如不同的当前编译器以不同的方式执行规则的事实所证明的那样。

例如,请参阅此测试代码。我使用 Apple LLVM Compiler 4.1、GCC 4.7.2 和 Visual Studio 2010 编译了这段代码。他们报告的错误有相似之处,也有不同之处。

class Base
{
protected:
    int A;
};
class Derived : public Base
{
protected:
    int B;
};
class Grandchild : public Derived
{
    void access_protected(Base* b, Derived* d,
                          Grandchild* g, class GreatGrandchild* gg );
};
class GreatGrandchild : public Grandchild {};
void Grandchild::access_protected(Base* b, Derived* d,
                                  Grandchild* g, GreatGrandchild* gg )
{
    int* p;
    Base lb;
    Derived ld;
    Grandchild lg;
    GreatGrandchild lgg;
    A = 1;              // Legal...
    B = 2;
    Base::A = 1;
    Derived::B = 2;
    b->A = 1;           // Illegal ALL
    p = &(b->A);        // Illegal ALL
    lb.A = 1;           // Illegal ALL
    p = &(lb.A);        // Illegal ALL
    d->A = 1;           // Illegal GCC, VS
    p = &(d->A);        // Illegal GCC, VS
    ld.A = 1;           // Illegal GCC, VS
    p = &(ld.A);        // Illegal GCC, VS
    d->B = 2;           // Illegal ALL
    p = &(d->B);        // Illegal ALL
    ld.B = 2;           // Illegal ALL
    p = &(ld.B);        // Illegal ALL
    g->A = 1;           // Legal...
    g->B = 2;
    lg.A = 1;
    lg.B = 2;
    gg->A = 1;
    gg->B = 2;
    lgg.A = 1;
    lgg.B = 2;
}

从这些结果中,我发现:(1) 访问你自己的类和派生类的受保护成员总是可以的;(2) 访问声明它们的基类的受保护成员始终是非法的,除了来自该类的基类;(3)尽管标准注意区分指向成员的指针和"对象表达式",但标准和编译器都给了它们相同的限制;(4)目前尚不清楚访问"中间"基类(示例中Derived)的受保护成员是否合法,该成员在中间基的基中声明。

那么,令人困惑的是,我是否可以谈论我祖父母的受保护成员归我父母所有。没有双关语的意图。

(为了简单和理智,我忽略了friends

由于protected是语言的基本组成部分,因此我有动力很好地理解它。请:

  1. 根据您对标准的解释,哪个编译器正确实现了这一点?
  2. 总体限制的理由是什么?为什么我不能自由访问基类的受保护成员?它旨在避免什么具体错误?您是否知道在线讨论(最好是由标准委员会举行的)来探讨这一基本原理?

我会说GCC和Visual Studio是正确的。

鉴于以下情况:

class Base
{
protected:
  int A;
};
class Derived : public Base
{
protected:
  int B;
};
class OtherGrandchild : Derived
{
};
class Grandchild : Derived
{
  void access_protected(OtherGrandchild* otherGrandchild);
};
void Grandchild::access_protected(OtherGrandchild* otherGrandchild)
{
  otherGrandchild->A = 1; // Should be illegal
  otherGrandchild->B = 1; // Should be illegal
  Derived* derived = static_cast<Derived*>(otherGrandchild);
  derived->A = 1; // Should still be illegal
  derived->B = 1; // Should still be illegal
}

如果您不受限制,则只需强制转换为公共基类型,即可将OtherGrandchild的私有成员从Grandchild更改为私有成员。这种访问只能通过好友声明来允许。

我不确定是否有关于这个话题的任何讨论,但这是我对它的解释。

这样做

的原因是派生对象 D 只能访问基的受保护成员,使用的对象表达式是 D 或另一个类派生自 D。

标准不允许 D 使用类型 B 的对象表达式访问另一个对象的受保护成员

这就是上面引用想要表达的确切内容(大致确定)。

class base
{
protected:
   int x;
};
class derived : public base
{
public:
   void f(base *p)   
   {
      x = 2;     // good
      p->x = 3;  // not good. base is not 'derived' nor derived from 'derived'
   }
};
int main() { }

想一想。基 B 的公共成员在任何派生类 D 中均可访问;B 的私有成员在任何派生类 D 中均不可访问。只有受保护的成员需要一些考虑。上述《标准》引述阐明了这一考虑。B 的受保护非静态成员只能在派生类 D 中访问,只能使用类型 D 的对象表达式或从 D 派生的类型

我不能引用参考或权威来源,但在我看来,g++ 和 VS 在这里都是正确的(这是他们第一次同意?),因为空的中间注入类可以改变父级受保护数据的访问控制根本没有逻辑意义。

至于基本原理,几乎可以肯定是因为限制越少protected它就越像public(它已经与公共有很多相似之处,因为你只需要派生一个类就可以不受限制地访问父内部)。如果您开始允许甚至不是同一实例的子类操纵您的状态,则违反类不变量的机会会大大增加。