简体   繁体   English

C ++ protected:无法从派生类中访问base的受保护成员

[英]C++ protected: fail to access base's protected member from within derived class

Admittedly, this question title sounds pretty much exactly the same as the question you neighbour Mike has repeatedly asked. 不可否认,这个问题标题听起来与迈克一再提出的邻居问题完全相同。 I found quite a few questions worded the same way, but none was what my question is about. 我发现了很多相同问题的问题,但没有一个是我的问题。

First of all, I'd like to clarify a few points for the context of this question: 首先,我想澄清一下这个问题的背景:

1, c++ access control works on a class basis rather than instance basis. 1,c ++访问控制基于类而不是基于实例。 Therefore, the following code is completely valid. 因此,以下代码完全有效。

class Base
{
protected:
    int b_;

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

2, I completely understand why the following code is NOT valid - another can be a sibling instance. 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_;
    }
};

Now time to unload my real question: 现在是时候卸载我真正的问题了:

Assume in the Derived class, I have an array of Base instances. 假设在Derived类中,我有一个Base实例数组。 So effectively, Derived IS A Base(IS-A relation), and Derived consists of Base(Composite relation). 如此有效,Derived IS A Base(IS-A关系),Derived由Base(复合关系)组成。 I read from somewhere that this(refers to the design of both IS-A and Has-A) is a design smell and I should never have a scenario like this in the first place. 我从某个地方读到这(指的是IS-A和Has-A的设计)是一种设计气味,我不应该首先有这样的场景。 Well, the mathematical concept of Fractals, for example, can be modelled by both IS-A and Has-A relations. 嗯,例如,Fractals的数学概念可以通过IS-A和Has-A关系建模。 However, let's disregard the opinion on design for a moment and just focus on the technical problem. 但是,让我们暂时忽略对设计的看法,只关注技术问题。

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
    }
};

The error message has already stated the error very clearly, so there's no need to repeat that in your answer: 错误消息已经非常清楚地说明了错误,所以在答案中无需重复:

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

Really, according to the following 2 facts, the code above should work: 真的,根据以下2个事实,上面的代码应该有效:

1, C++ access control works on class basis rather than instance basis(therefore, please don't say that I can only access Derived's b_; I can't access a stand alone Base instance's protected members - it's on class basis). 1,C ++访问控制基于类而不是基于实例(因此,请不要说我只能访问Derived的b_;我无法访问独立的Base实例的受保护成员 - 它是基于类的)。

2, Error message says "within this context" - the context is Derived(I was trying to access a Base instance's protected member from within Derived. It's the very feature of a protected member - it should be able to be accessed from within Base or anything that derives from Base. 2,错误消息显示“在此上下文中” - 上下文是Derived(我试图从Derived中访问Base实例的受保护成员。这是受保护成员的特性 - 它应该能够从Base内部访问或者任何来自Base的东西。

So why is the compiler giving me this error? 那么为什么编译器会给我这个错误呢?

The access rules could in principle have provided an exemption for this special case, where it's known that Base is the most derived class , the dynamic type of the object. 访问规则原则上可以为这种特殊情况提供豁免,其中已知Base最派生的类 ,即对象的动态类型。 But that would have complicated things. 但这会让事情变得复杂。 C++ is sufficiently complicated. C ++足够复杂。

A simple workaround is to provide a static protected accessor function up in Base . 一个简单的解决方法是在Base提供static protected访问器功能。

A more hack'ish workaround is to use the infamous type system loophole for member pointers. 更黑客的解决方法是使用臭名昭着的类型系统漏洞来获取成员指针。 But I'd go for the static function, if I had to stick with the basic design. 但是,如果我必须坚持基本设计,我会选择static功能。 Because I think like there's not much point in saving a few keystrokes when the resulting code is both hard to get right in the first place, and hard to understand for maintainers. 因为我觉得在保存一些按键时没有多少意义,因为最终的代码很难在正确的情况下完成,并且难以理解维护者。


Concrete example: 具体例子:

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
    }
};

2, Error message says "within this context" - the context is Derived(I was trying to access a Base instance's protected member from within Derived. It's the very feature of a protected member- it should be able to be accessed from within Base or anything that derives from Base. 2,错误消息显示“在此上下文中” - 上下文是Derived(我试图从Derived中访问Base实例的受保护成员。这是受保护成员的特性 - 它应该能够从Base内部访问或者任何来自Base的东西。

Okay, had to go to the standard for this one. 好的,必须达到这一标准。

So you're asking, "Why isn't it possible?" 所以你问,“为什么不可能?” The answer: Because of how the standard really defines protected member access: 答案:由于标准如何真正定义受保护的成员访问:

§ 11.4 Protected member access §11.4受保护的成员访问权限

[1] An additional access check beyond those described earlier in Clause 11 is applied when a non-static data member or non-static member function is a protected member of its naming class...As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C . [1]当非静态数据成员或非静态成员函数是其命名类的受保护成员时,将应用超出第11章中所述之外的其他访问检查...如前所述,访问受保护成员是因为引用发生在某个C类的朋友或成员中

(emphasis mine) (强调我的)

So let's go over your examples to see what's what. 那么让我们回顾一下你的例子,看看它是什么。

class Base
{
protected:
    int b_;

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

No problem. 没问题。 another.b_ is Base::b_ , and we're accessing it from a member function Base::IsEqual(const Base&) const . 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_;
    }
};

Here, we're accessing Base::b_ again, but our context is a member function Derived::IsEqual_Another(const Base&) const , which isn't a member of Base . 这里,我们再次访问Base::b_ ,但我们的上下文是一个成员函数Derived::IsEqual_Another(const Base&) const ,它不是Base的成员。 So no go. 所以没有去。

Now for the alleged culprit. 现在是涉嫌罪魁祸首。

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_ is accessing the protected Base::b_ , inside the context of Derived::TestFunc() , which isn't a member (or friend...) of Base . bases_[0].b_正在Derived::TestFunc()的上下文中访问受保护的Base::b_ ,它不是Base的成员(或朋友......)。

So looks like the compiler is acting in accordance with the rules. 所以看起来编译器按照规则行事。

I am just turning my comments into an answer because I find the issue interesting. 我只是将我的评论转化为答案,因为我觉得这个问题很有意思。 In particular that in the following minimal example D doesn't compile baffled me: 特别是在下面的最小例子中D不会编译困惑我:

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

After all, a D is a B and should be able to do all that a B can do (except access B 's private members), shouldn't it? 毕竟, DB ,应该能够完成B可以做的所有事情(访问B的私人成员除外),不应该吗?

Apparently, the language designers of both C++ and C# found that too lenient. 显然,C ++和C#的语言设计者发现它太宽松了。 Eric Lippert commented one of his own blog posts saying Eric Lippert 评论了他自己的一篇博文

But that's not the kind of protection we've chosen as interesting or valuable. 但这并不是我们选择的有趣或有价值的保护。 "Sibling" classes do not get to be friendly with each other because otherwise protection is very little protection. “兄弟姐妹”课程彼此之间并不友好,因为否则保护很少受到保护。

EDIT: 编辑:
Because there seems to be some confusion about the actual rule laid forth in 11.4 I'll parse it and illustrate the basic idea with a short example. 因为对于11.4中提出的实际规则似乎存在一些混淆,我将解析它并用一个简短的例子来说明基本思想。

  1. The purpose of the section is laid out, and what it applies to (non-static members). 本节的目的是什么,以及适用于(非静态成员)的内容。

    An additional access check beyond those described earlier in Clause 11 is applied when a non-static data member or non-static member function is a protected member of its naming class (11.2) 当非静态数据成员或非静态成员函数是其命名类的受保护成员时,将应用超出第11章中所述之外的其他访问检查(11.2)

    The naming class in the example below is B . 以下示例中的命名类是B

  2. Context is established by summarising the chapter so far (it defined access rules for protected members). 通过总结到目前为止的章节(它定义了受保护成员的访问规则)来建立上下文。 Additionally a name for a "class C" is introduced: Our code is supposed to reside inside a member or friend function of C, ie has C's access rights. 另外,引入了“C类”的名称:我们的代码应该驻留在C的成员或朋友函数内,即具有C的访问权限。

    As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. 如前所述,授予对受保护成员的访问权限,因为引用发生在某个类C的朋友或成员中

    "Class C" is also class C in the example below. 在下面的例子中,“C类”也是C类。

  3. Only now the actual check is defined. 只有现在才定义实际检查。 The first part deals with pointers to members, which we ignore here. 第一部分涉及指向成员的指针,我们在此处忽略。 The second part concerns your everyday accessing a member of an object, which logically "involve a (possibly implicit) object expression " . 第二部分涉及您每天访问一个对象的成员,这在逻辑上“涉及(可能是隐式的) 对象表达
    It's just the last sentence which describes the "additional check" this whole section was for: 这只是描述整个部分的“附加检查”的最后一句话:

    In this case, the class of the object expression [through which the member is accessed -pas] shall be C or a class derived from C. 在这种情况下,对象表达式的类 [通过其访问成员-pas] 应为C或从C派生的类。

    The "object expression" can be things like a variable, a return value of a function, or a dereferenced pointer. “对象表达式”可以是变量,函数的返回值或解除引用的指针。 The "class of the object expression" is a compile time property, not a run time property; “对象表达式的类”是编译时属性,而不是运行时属性; access through one and the same object may be denied or granted depending on the type of the expression used to access the member. 根据用于访问成员的表达式的类型,可以拒绝或授予通过同一个对象的访问权限。

This code snippet demonstrates that. 此代码段演示了这一点。

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;
    }
};

I initially wondered about this rule and assume the following rationale: 我最初想知道这个规则,并假设以下理由:

The issue at hand is data ownership and authority. 手头的问题是数据所有权和权限。

Without code inside B explicitly providing access (by making C a friend or by something like Alf's static accessor) no other classes except those who "own" the data are allowed to access it. 如果没有内部代码B明确提供访问(通过使C朋友或类似阿尔夫的静态访问),没有其他类除了那些谁“拥有”的数据被允许访问它。 This prevents gaining illicit access to the protected members of a class by simply defining a sibling and modifying objects of the original derived class through the new and before unknown sibling. 这可以通过简单地定义兄弟并通过新的和未知的兄弟之前修改原始派生类的对象来防止非法访问类的受保护成员。 Stroustrup speaks of "subtle errors" in this context in the TCPPL. Stroustrup在TCPPL中谈到了“细微错误”。

While it would be safe to access (different) objects of the original base class from a derived class' code, the rule is simply concerned with expressions (a compile time property) and not objects (a run time property). 虽然从派生类的代码访问原始基类的(不同的)对象是安全的,但规则只关注表达式 (编译时属性)而不是对象(运行时属性)。 While static code analysis may show that an expression of some type Base actually never refers to a sibling, this is not even attempted, similar to the rules concerning aliasing. 虽然静态代码分析可能会显示某种类型Base的表达式实际上从未引用同级,但这甚至都没有尝试过,类似于有关别名的规则。 (Maybe that is what Alf meant in his post.) (也许这就是Alf在他的帖子中的意思。)

I imagine the underlying design principle is the following: Guaranteeing ownership and authority over data gives a class the guarantee that it can maintain invariants related to the data ("after changing protected a always also change b "). 我想象中的基本设计原理是:保障所有权和权威的数据提供了一个类的保证,它可以保持与数据相关的不变量(“改变保护后a永远也改变b ”)。 Providing the possibility to change a protected property from by a sibling may break the invariant -- a sibling does not know the details of its sibling's implementation choices (which may have been written in a galaxy far, far away). 提供从兄弟姐妹那里改变受保护财产的可能性可能打破不变性 - 兄弟姐妹不知道其兄弟姐妹的实施选择的细节(可能已经远远地写在银河系中)。 A simple example would be a Tetragon base class with protected width and height data members plus trivial public virtual accessors. 一个简单的例子是Tetragon基类,它具有受保护的widthheight数据成员以及普通的公共虚拟访问器。 Two siblings derive from it, Parallelogram and Square . 两个兄弟姐妹来自它, ParallelogramSquare Square 's accessors are overridden to always also set the other dimension in order to preserve a square's invariant of equally long sides, or they only just use one of the two. Square的存取器被覆盖,以便始终设置另一个维度,以保持方形的长边不变,或者它们只使用两者中的一个。 Now if a Parallelogram could set a Square 's width or height directly through a Tertragon reference they would break that invariant. 现在,如果Parallelogram可以直接通过Tertragon参考设置Squarewidthheight它们将打破该不变量。

This has nothing to do with bases_ being protected in Derived , it is all about b_ being protected in Base . 这与在Derived中受保护的bases_无关,它是关于b_Base中受保护的全部内容。

As you have already stated, Derived can only access protected members of its base class, not of any other Base objects. 正如您已经说过的, Derived只能访问其基类的受保护成员,而不能访问任何其他Base对象。 Not even if they are members of Derived . 即使他们是Derived成员也不会。

If you really need access, you can make Derived a friend on Base . 如果你真的需要访问,可以使Derived一个朋友Base

Ok, I've been bothered by this wicked thing for a night. 好吧,我被这个邪恶的事情困扰了一晚。 Endless discussions and the ambiguity of clause 11.4(as quoted by Yam marcovic) 无休止的讨论和第11.4条的含糊不清(由Yam marcovic引述)

§ 11.4 Protected member access §11.4受保护的成员访问权限

[1] An additional access check beyond those described earlier in Clause 11 is applied when a non-static data member or non-static member function is a protected member of its naming class...As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. [1]当非静态数据成员或非静态成员函数是其命名类的受保护成员时,将应用超出第11章中所述之外的其他访问检查...如前所述,访问受保护成员是因为引用发生在某个类C的朋友或成员中。

have burned me out. 把我烧了 I decided to resort to the gcc source code(gcc 4.9.2 in my case) to check how those gcc guys understood the clause 11.4, and what check exactly the C++ standards wants to do and how those checks are supposed to be done. 我决定使用gcc源代码(在我的情况下为gcc 4.9.2)来检查那些gcc人员如何理解第11.4节,以及确切地检查C ++标准想要做什么以及应该如何完成这些检查。

In gcc/cp/search.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;
}

The most interesting part is this: 最有趣的部分是:

  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 in the code is the context, which in my case is the Derived class; 1)在代码中派生的是上下文,在我的例子中是Derived类;

2) binfo in the code represents the instance whose non-static protected member is access, which in my case is base_, Derived's protected data member Base instance; 2)代码中的binfo表示非静态受保护成员访问的实例,在我的例子中是base_,Derived的受保护数据成员Base实例;

3) decl in the code represents base_.b_. 3)代码中的decl代表base_.b_。

What gcc did when translating my code in question was: 在翻译我的代码时,gcc做了什么:

1) check if base_.b_ is non-static protected member? 1)检查base_.b_是否是非静态保护成员? yes of course, so enter the if; 是的当然,所以输入if;

2) climb up the inheritance tree of base_; 2)爬上base_的继承树;

3) figure out what actual type base_ is; 3)弄清楚实际类型base_是什么; of course, it's Base 当然,这是基地

4) check if the result in 3) which is Base, derives from Derived. 4)检查3)中的结果是否为Base,派生自Derived。 Of course that's a negative. 当然这是消极的。 Then return 0 - access denied. 然后返回0 - 访问被拒绝。

Apparently, according to gcc's implementation, the "additional check" requested by the C++ standard is the type check of the instance through which the protected member gets accessed. 显然,根据gcc的实现,C ++标准请求的“附加检查”是受保护成员通过其访问的实例的类型检查。 Although the C++ standard did not explicitly mention what check should be done, I think gcc's check is the most sensible and plausible one - it's probably the kind of check the C++ standard wants. 虽然C ++标准没有明确提到应该做什么检查,但我认为gcc的检查是最明智和最合理的检查 - 它可能是C ++标准所要求的检查。 And then the question really boils down to the rationale for the standard to request an additional check like this. 然后问题真的归结为标准要求这样的额外检查的理由。 It effectively makes the standard contradict itself. 它有效地使标准自相矛盾。 Getting rid of that interesting section(It seems to me that the C++ standard is asking for inconsistency deliberately), the code should work perfectly. 摆脱那个有趣的部分(在我看来,C ++标准是故意要求不一致),代码应该完美地工作。 In particular, the sibling problem won't occur as it will be filtered by the statement: 特别是,兄弟问题不会发生,因为它将被语句过滤:

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

Regarding the kind of protection(protected does not work purely on class, but on BOTH class AND instance) mentioned by Peter and the post(by Eric Lippert) he shared, I personally totally agree with that. 关于彼得和他所分享的帖子(Eric Lippert)提到的保护类型(受保护不仅仅是在课堂上,但在BOTH类和实例上),我个人完全同意这一点。 Unfortunately, by looking at the C++ standard's wording, it doesn't; 不幸的是,通过查看C ++标准的措辞,它没有; if we accept that the gcc implementation is an accurate interpretation of the standard, then what the C++ standard really asks for is, a protected member can be accessed by its naming class or anything that derives from the naming class; 如果我们接受gcc实现是对标准的准确解释,那么C ++标准真正要求的是,受保护的成员可以通过其命名类或从命名类派生的任何东西来访问; however, when the protected member is accessed via an object, make sure the owner object's type is the same as the calling context's type. 但是,当通过对象访问受保护的成员时,请确保所有者对象的类型与调用上下文的类型相同。 Looks like the standard just wants to make an exception for the clarification point 1 in my original question. 看起来标准只是想在我原来的问题中为澄清点1做一个例外。

Last but not least, I'd like to thank Yam marcovic for pointing out clause 11.4. 最后但同样重要的是,我要感谢Yam marcovic指出第11.4条。 You are the man, although your explanation wasn't quite right - the context does not have to be Base, it can be Base or anything derived from Base. 你是男人,虽然你的解释不太正确 - 上下文不一定是Base,它可以是Base或任何派生自Base的东西。 The catch was in the type check of the instance through which the non-static protected member was accessed. catch是在实例的类型检查中,通过该实例访问非静态受保护成员。

There are a couple of long answers, and quotes from the standard that are correct. 有几个很长的答案,标准的引用是正确的。 I intend on providing a different way of looking at what protected really means that might help understanding. 我打算提供一种不同的方式来看看保护真正意味着哪些可能有助于理解。

When a type inherits from a different type, it gets a base sub-object. 当类型继承自不同类型时,它将获得基础子对象。 The protected keyword means that any derived type can access this particular member within the sub-object that it contains due to the inheritance relationship. protected关键字意味着由于继承关系,任何派生类型都可以访问它包含的子对象中的此特定成员。 The keyword grants access to specific object (s), not to any object of type base. 关键字授予对特定对象的访问权限,而不授予对类型为base的任何对象的访问权限。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM