Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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. 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: 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++ 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, 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?

like image 386
h9uest Avatar asked Jan 04 '16 10:01

h9uest


People also ask

How do you access protected members in a derived class?

A class in C++ has public, private and protected sections which contain the corresponding class members. Protected members in a class are similar to private members as they cannot be accessed from outside the class. But they can be accessed by derived classes or child classes while private members cannot.

Do derived classes inherit protected members?

protected inheritance makes the public and protected members of the base class protected in the derived class. private inheritance makes the public and protected members of the base class private in the derived class.

Can we access protected member derived class in Java?

Protected Access Modifier - Protected Variables, methods, and constructors, which are declared protected in a superclass can be accessed only by the subclasses in other package or any class within the package of the protected members' class. The protected access modifier cannot be applied to class and interfaces.

Can we access protected member derived class in C#?

A protected member of a base class is accessible in a derived class only if the access takes place through the derived class type.


3 Answers

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. But that would have complicated things. C++ is sufficiently complicated.

A simple workaround is to provide a static protected accessor function up in Base.

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. 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
    }
};
like image 107
Cheers and hth. - Alf Avatar answered Oct 22 '22 17:10

Cheers and hth. - Alf


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.

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

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

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

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

So looks like the compiler is acting in accordance with the rules.

like image 2
Yam Marcovic Avatar answered Oct 22 '22 18:10

Yam Marcovic


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

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?

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.

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.

  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)

    The naming class in the example below is 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, i.e. has C's access rights.

    As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C.

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

  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.

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

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

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.

like image 2
Peter - Reinstate Monica Avatar answered Oct 22 '22 16:10

Peter - Reinstate Monica