1

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

当非静态数据成员或非静态成员函数是其命名类 (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) 尽管标准注意区分指向成员的指针和“对象表达式”,但标准和编译器都给了它们相同的限制;Derived(4) 目前尚不清楚访问“中间”基类(在示例中)的受保护成员是否合法,该成员在中间基类中声明。

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

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

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

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

3 回答 3

0

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

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

于 2013-05-16T13:38:00.317 回答
0

我会说 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
}

如果您没有受到限制,您可以通过转换为通用基类型来更改OtherGrandchildfrom的其他私有成员。Grandchild这种访问应该只允许通过友元声明。

我不确定有关该主题的任何讨论,但这将是我对它的解释。

于 2013-01-25T15:13:38.587 回答
0

这样做的原因是派生对象 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 中访问

于 2013-01-25T15:36:22.993 回答