受C ++保护:无法从派生类

时间:2016-01-04 10:20:33

标签: c++ inheritance access-control protected

不可否认,这个问题标题听起来与迈克一再提出的邻居问题完全相同。我发现有很多问题措辞相同,但没有一个是我的问题。

首先,我想就这个问题的背景澄清几点:

1,c ++访问控制基于类而不是基于实例。因此,以下代码完全有效。

class Base
{
protected:
    int b_;

public:
    bool IsEqual(const Base& another) const
    {
        return another.b_ == b_; // access another instance's protected member
    }
};

2,我完全理解为什么以下代码无效 - 另一个代码可能是兄弟实例。

class Derived : public Base
{
public:
    // to correct the problem, change the Base& to Derived&
    bool IsEqual_Another(const Base& another) const
    {
        return another.b_ == b_;
    }
};

现在是时候卸载我真正的问题了:

假设在Derived类中,我有一个Base实例数组。如此有效,Derived IS A Base(IS-A关系),Derived由Base(复合关系)组成。我从某个地方读到这(指的是IS-A和Has-A的设计)是一种设计气味,我不应该首先有这样的场景。嗯,例如,Fractals的数学概念可以通过IS-A和Has-A关系建模。但是,让我们暂时忽视对设计的看法,只关注技术问题。

class Derived : public Base
{
protected:
    Base base_;

public:
    bool IsEqual_Another(const Derived& another) const
    {
        return another.b_ == b_;
    }

    void TestFunc()
    {
        int b = base_.b_; // fail here
    }
};

错误消息已经非常清楚地说明了错误,因此您无需在答案中重复该错误:

  

Main.cpp:140:7:错误:'int Base :: b_'受保护      int b_;          ^   Main.cpp:162:22:错误:在此上下文中       int b = base_.b _;

真的,根据以下两个事实,上面的代码应该有效:

1,C ++访问控制基于类而不是基于实例(因此,请不要说我只能访问Derived&#39 ;;我无法访问独立的Base实例& #39;受保护的成员 - 它是基于班级的。)

2,错误信息显示"在此上下文中#34; - 上下文是Derived(我试图从Derived中访问Base实例的受保护成员。它是受保护成员的特征 - 它应该能够从Base内部或任何来自Base。

那么为什么编译器会给我这个错误呢?

6 个答案:

答案 0 :(得分:2)

原则上,访问规则可以为这种特殊情况提供豁免,其中已知Base最派生类,即对象的动态类型。但这会让事情变得复杂。 C ++非常复杂。

一个简单的解决方法是在static中提供protected Base访问者功能。

更糟糕的解决方法是使用臭名昭着的类型系统漏洞来获取成员指针。但是,如果我不得不坚持基本设计,我会选择static功能。因为我认为,如果最终的代码难以正确处理并且维护人员难以理解,那么在保存一些按键时没有多大意义。

具体例子:

class Base
{
protected:
    int b_;

    static
    auto b_of( Base& o )
        -> int&
    { return o.b; }

public:
    auto IsEqual( const Base& another ) const
        -> bool
    {
        return another.b_ == b_; // access another instance's protected member
    }
};

答案 1 :(得分:2)

  

2,错误消息显示“在此上下文中” - 上下文是Derived(我试图从Derived中访问Base实例的受保护成员。这是受保护成员的特征 - 它应该能够从在Base或任何派生自Base的内容。

好的,必须达到这个标准。

所以你问,“为什么不可能?”答案:由于标准确实如何定义受保护的成员访问:

  

§11.4   受保护的成员访问

     

[1]   当非静态数据时,将应用超出前面第11章中描述的附加访问检查   成员或非静态成员函数是其命名类的受保护成员...如上所述   之前,授予对受保护成员的访问权限,因为引用发生在朋友或某个C类成员中。

(强调我的)

让我们回顾一下你的例子,看看它是什么。

class Base
{
protected:
    int b_;

public:
    bool IsEqual(const Base& another) const
    {
        return another.b_ == b_; // access another instance's protected member
    }
};

没问题。 another.b_Base::b_,我们正在从成员函数Base::IsEqual(const Base&) const访问它。

class Derived : public Base
{
public:
    // to correct the problem, change the Base& to Derived&
    bool IsEqual_Another(const Base& another) const
    {
        return another.b_ == b_;
    }
};

在这里,我们再次访问Base::b_,但我们的上下文是成员函数Derived::IsEqual_Another(const Base&) const,它不是Base的成员。所以不要去。

现在是涉嫌罪魁祸首。

class Derived : public Base
{
protected:
    Base bases_[5];

public:
    bool IsEqual_Another(const Derived& another) const
    {
        return another.b_ == b_;
    }

    void TestFunc()
    {
        int b = bases_[0].b_; // fail here
    }
};

bases_[0].b_正在Base::b_的上下文中访问受保护的Derived::TestFunc(),该Base不是require(arules) #require(arulesViz) #some code to retreive the my_matrix #my_matrix my_rows= nrow(my_matrix); my_cols= ncol(my_matrix); matrix_temp = my_matrix[,4:5]; matrix_temp = array(c(matrix_temp, my_matrix[,20]), dim=c(my_rows,3)) #matrix_temp my_matrix = matrix_temp sum(is.na(my_matrix)) #output: [1] 0 my_transactions = as(my_matrix, "transactions"); summary(my_transactions) 的成员(或朋友......)。

所以看起来编译器按照规则行事。

答案 2 :(得分:2)

我只是将我的评论转化为答案,因为我觉得这个问题很有意思。特别是在下面的最小例子中D编译困惑我:

class B            { protected: int i;          };
class D : public B { int f(B &b){ return b.i; } };

毕竟,DB并且应该能够完成B可以执行的所有工作(访问B的私人成员除外),不应该不是吗?

显然,C ++和C#的语言设计者发现它太宽松了。 Eric Lippert commented one of his own blog posts

  

但这不是我们选择的有趣或有价值的保护。 “兄弟姐妹”课程彼此友好,因为否则保护很少保护。

修改
因为似乎对11.4中提出的实际规则存在一些混淆,我将解析它并用一个简短的例子说明基本思想。

  1. 本节的目的是什么,以及适用于(非静态成员)的内容。

      

    除了前面第11章中描述的那些之外的附加访问检查   在非静态数据成员或非静态成员函数时应用   是其命名类(11.2)的受保护成员

    以下示例中的命名类为B

  2. 通过总结到目前为止的章节(它定义了受保护成员的访问规则)来建立上下文。另外,引入了“C类”的名称:我们的代码应该驻留在C的成员或朋友函数内,即具有C的访问权限。

      

    如前所述,访问受保护的成员是   因为   引用发生在朋友或某些成员中    C级。

    “C类”在下面的示例中也是课程C

  3. 现在只定义实际检查。第一部分涉及指向成员的指针,我们在此处忽略。第二部分涉及您每天访问对象的成员,逻辑上“涉及(可能隐含的)对象表达。 这只是描述整个部分的“附加检查”的最后一句话:

      

    在这种情况下,对象表达式的类   [通过其访问成员-pas]   应为C或源自C的类。

    “对象表达式”可以是变量之类的东西, 函数的返回值或解除引用的指针。 “对象表达式的类”是编译时 财产,而不是运行时财产;通过一个访问 并且可以拒绝或授予相同的对象 on用于访问该成员的表达式的类型。

  4. 此代码段演示了这一点。

    class B { protected: int b; };
    
    class C: public B 
    {
        void f()
        {
            // Ok. The expression of *this is C (C has an
            // inherited member b which is accessible 
            // because it is not declared private in its
            // naming class B).
            this->b = 1;    
    
            B *pb = this;
    
            // Not ok -- the compile time 
            // type of the expression *pb is B.
            // It is not "C or a class derived from C"
            // as mandated by 11.4 in the 2011 standard.
            pb->b = 1;
        }
    };
    

    我最初想知道这个规则,并假设以下理由:

    手头的问题是数据所有权和权限。

    没有 B内的代码明确提供访问权限(通过使C成为朋友或类似Alf的静态访问者)除了“拥有”数据的人之外没有其他类被允许访问它。这可以通过简单地定义兄弟并通过新的和未知的兄弟之前修改原始派生类对象来防止非法访问类的受保护成员。 Stroustrup在TCPPL中谈到了“细微错误”。

    虽然从派生类的代码访问原始基类的(不同的)对象是安全的,但该规则只关注表达式(编译时属性)而不是对象(运行时属性)。虽然静态代码分析可能表明某种类型Base的表达式实际上从未引用同级,但这甚至都没有尝试过,类似于有关别名的规则。 (也许这就是Alf在他的帖子中的意思。)

    我认为基本的设计原则如下:保证对数据的所有权和权限,使类能够保证它可以维护与数据相关的不变量(“更改后的a总是也会更改{{1} }“)。提供从兄弟姐妹那里改变受保护财产的可能性可能打破不变性 - 兄弟姐妹不知道其兄弟姐妹的实施选择的细节(可能已经远远地写在银河系中)。一个简单的示例是b基类,其中包含受保护的Tetragonwidth数据成员以及普通的公共虚拟访问器。两个兄弟姐妹来自它,heightParallelogramSquare的访问器被覆盖,以便始终设置另一个维度,以便保留正方形的同等长边不变量,或者它们只使用两者中的一个。现在,如果Square可以通过Parallelogram引用直接设置Square的{​​{1}}或width,则会破坏该不变量。

答案 3 :(得分:1)

这与bases_Derived受保护无关,b_Base保护Derived

正如您已经说过的那样,Base只能访问其基类的受保护成员,而不能访问任何其他Derived对象。即使他们是Derived的成员,也不是。

如果确实需要访问权限,您可以Baseisset($_POST['ulozitzmeny'])上成为朋友。

答案 4 :(得分:0)

好的,我被这个邪恶的东西困扰了一晚。无休止的讨论和第11.4条的含糊不清(由Yam marcovic引用)

  

§11.4受保护的成员访问

     

[1]当非静态数据成员或非静态成员函数是其命名类的受保护成员时,将应用超出第11章中所述之外的其他访问检查...如前所述,访问a保护成员被授予,因为引用发生在某个C类的朋友或成员中。

烧毁了我。我决定使用gcc源代码(在我的情况下是gcc 4.9.2)来检查那些gcc人员如何理解第11.4节,以及确切地检查C ++标准想要做什么以及应该如何进行这些检查。< / p>

在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)在代码中派生的是上下文,在我的例子中是Derived类;

2)代码中的binfo表示非静态受保护成员访问的实例,在我的例子中是base_,Derived的受保护数据成员Base实例;

3)代码中的decl代表base_.b _。

gcc在翻译我的代码时所做的是:

1)检查base_.b_是否是非静态保护成员?当然是的,所以输入if;

2)爬上base _的继承树;

3)找出实际类型base_是什么;当然,这是基地

4)检查3)中的结果是否为Base,派生自Derived。当然这是消极的。然后返回0 - 拒绝访问。

显然,根据gcc的实现,C ++标准请求的“附加检查”是受保护成员通过其访问的实例的类型检查。虽然C ++标准没有明确提到应该做什么检查,但我认为gcc的检查是最明智和最合理的检查 - 它可能是C ++标准所要求的检查。然后问题真的归结为标准要求这样的额外检查的理由。它有效地使标准自相矛盾。摆脱那个有趣的部分(在我看来,C ++标准是故意要求不一致),代码应该完美地工作。特别是,兄弟问题不会发生,因为它将被语句过滤:

if (!DERIVED_FROM_P(BINFO_TYPE(t), derived))
      return 0;

关于彼得和他所分享的帖子(Eric Lippert)提到的保护类型(受保护不仅仅是在课堂上,但在BOTH类AND实例上),我个人完全同意这一点。不幸的是,通过查看C ++标准的措辞,它没有;如果我们接受gcc实现是对标准的准确解释,那么C ++标准真正要求的是,受保护的成员可以通过其命名类或从命名类派生的任何东西来访问;但是,当通过对象访问受保护的成员时,请确保所有者对象的类型与调用上下文的类型相同。看起来标准只是想在我原来的问题中对澄清点1做出例外。

最后但同样重要的是,我要感谢Yam marcovic指出第11.4条。你是男人,虽然你的解释不太正确 - 上下文不一定是Base,它可以是Base或任何派生自Base的东西。 catch是在实例的类型检查中,通过该实例访问非静态受保护成员。

答案 5 :(得分:0)

有几个很长的答案,标准的引用是正确的。我打算提供一种不同的方式来看看保护真正意味着哪些可能有助于理解。

当类型继承自不同类型时,它会获得 base 子对象。 protected关键字表示由于继承关系,任何派生类型都可以访问其包含的子对象中的此特定成员。该关键字授予对特定对象的访问权限,而不授予任何类型为base的对象。