C++ 受保护:无法从派生类中访问基类的受保护成员 [英] C++ protected: fail to access base's protected member from within derived class

查看:41
本文介绍了C++ 受保护:无法从派生类中访问基类的受保护成员的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

诚然,这个问题的标题听起来与您的邻居迈克反复提出的问题几乎完全相同.我发现很多问题的措辞都相同,但没有一个是我的问题.

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++ 访问控制是基于类而不是基于实例的.因此,以下代码完全有效.

1, c++ access control works on a class basis rather than instance basis. 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,我完全理解为什么下面的代码无效——另一个可以是兄弟实例.

2, I completely understand why the following code is NOT valid - another can be a sibling instance.

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:

假设在 Derived 类中,我有一个 Base 实例数组.如此有效,Derived IS A Base(IS-A关系),Derived由Base(复合关系)组成.我从某处读到这(指的是 IS-A 和 Has-A 的设计)是一种设计气味,我一开始就不应该有这样的场景.嗯,例如,分形的数学概念可以通过 IS-A 和 Has-A 关系建模.不过,让我们暂时不考虑设计的意见,只关注技术问题.

Assume in the Derived class, I have an array of Base instances. So effectively, Derived IS A Base(IS-A relation), and Derived consists of Base(Composite relation). 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. Well, the mathematical concept of Fractals, for example, can be modelled by both IS-A and Has-A relations. 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: 错误:'int Base::b_' 受保护国际b_;^Main.cpp:162:22: 错误:在此上下文中int b = base_.b_;

Main.cpp:140:7: error: ‘int Base::b_’ is protected int b_; ^ Main.cpp:162:22: error: within this context int b = base_.b_;

实际上,根据以下两个事实,上面的代码应该可以工作:

Really, according to the following 2 facts, the code above should work:

1、C++访问控制是基于类而不是基于实例的(因此,请不要说我只能访问Derived的b_;我不能访问独立的Base实例的受保护成员——它是基于类的).

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).

2,错误消息说在此上下文中" - 上下文是派生的(我试图从派生内访问 Base 实例的受保护成员.这是受保护成员的特性 - 它应该能够从在 Base 或从 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.

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

So why is the compiler giving me this error?

推荐答案

我只是把我的评论变成了一个答案,因为我觉得这个问题很有趣.特别是在下面的最小示例中 D 没有编译让我感到困惑:

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:

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

毕竟,DB 并且应该能够做 B 能做的所有事情(除了访问 B 的私有成员),不是吗?

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?

显然,C++ 和 C# 的语言设计者都觉得这太宽容了.Eric Lippert 评论了他自己的一篇博文

Apparently, the language designers of both C++ and C# found that too lenient. Eric Lippert commented one of his own blog posts saying

但这不是我们选择的有趣或有价值的保护.兄弟"班级不会彼此友好,因为否则保护是非常小的保护.

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.


因为对于 11.4 中提出的实际规则似乎有些混乱,我将对其进行解析并用一个简短的示例说明基本思想.


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.

  1. 阐述了该部分的目的,以及它适用于什么(非静态成员).

  1. The purpose of the section is laid out, and what it applies to (non-static members).

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

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)

下面例子中的命名类是B.

The naming class in the example below is B.

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

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, i.e. has C's access rights.

如前所述,对受保护成员的访问是授予,因为引用发生在某个朋友或成员身上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 类"在下面的例子中也是 C 类.

"Class C" is also class C in the example below.

现在才定义了实际的检查.第一部分处理指向成员的指针,我们在这里忽略.第二部分涉及您日常访问对象的成员,逻辑上涉及(可能是隐式的)对象表达式".
这只是描述整个部分的附加检查"的最后一句话:

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:

本例中为对象表达式的类[通过它访问成员-pas]应为 C 或从 C 派生的类.

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.

对象表达式"可以是变量之类的东西,函数的返回值,或取消引用的指针.对象表达式的类"是一个编译时属性,而不是运行时属性;通过一个访问并且可能会拒绝或授予相同的对象,具体取决于用于访问成员的表达式的类型.

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.

没有代码inside B 显式提供访问(通过使 C 成为朋友或通过诸如 Alf 的静态访问器之类的东西)没有其他类,除了那些允许拥有"数据的人访问它.这可以防止通过简单地定义同级并通过新的和之前的未知同级修改原始派生类的对象来非法访问类的受保护成员.Stroustrup 在 TCPPL 的上下文中谈到了微妙的错误".

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. 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.

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

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. (Maybe that is what Alf meant in his post.)

我想基本的设计原则如下:保证对数据的所有权和权限给一个类保证它可以维护与数据相关的不变量(在改变受保护的a之后总是也会改变<代码>b").提供从兄弟姐妹更改受保护属性的可能性可能会破坏不变量——兄弟姐妹不知道其兄弟姐妹的实现选择的细节(可能已经写在很远的星系中).一个简单的例子是一个 Tetragon 基类,它带有受保护的 widthheight 数据成员以及一些简单的公共虚拟访问器.两个兄弟姐妹从它派生出来,ParallelogramSquare.Square 的访问器被覆盖以始终设置另一个维度,以保持正方形的等长边不变量,或者它们只使用两者之一.现在,如果Parallelogram 可以直接通过Tertragon 设置Squarewidthheight代码> 引用他们会打破这个不变性.

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"). 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. Two siblings derive from it, Parallelogram and Square. 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. Now if a Parallelogram could set a Square's width or height directly through a Tertragon reference they would break that invariant.

这篇关于C++ 受保护:无法从派生类中访问基类的受保护成员的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆