accessing a protected member of a base class in another subclass

26,942

Solution 1

When foo receives a FooBase reference, the compiler doesn't know whether the argument is a descendant of Foo, so it has to assume it's not. Foo has access to inherited protected members of other Foo objects, not all other sibling classes.

Consider this code:

class FooSibling: public FooBase { };

FooSibling sib;
Foo f;
f.foo(sib); // calls sib.fooBase()!?

If Foo::foo can call protected members of arbitrary FooBase descendants, then it can call the protected method of FooSibling, which has no direct relationship to Foo. That's not how protected access is supposed to work.

If Foo needs access to protected members of all FooBase objects, not just those that are also known to be Foo descendants, then Foo needs to be a friend of FooBase:

class FooBase
{
protected:
  void fooBase(void);
  friend class Foo;
};

Solution 2

The C++ FAQ summarizes this issue nicely:

[You] are allowed to pick your own pockets, but you are not allowed to pick your father's pockets nor your brother's pockets.

Solution 3

The key point is that protected grants you access to your own copy of the member, not to those members in any other object. This is a common misconception, as more often than not we generalize and state protected grants access to the member to the derived type (without explicitly stating that only to their own bases...)

Now, that is for a reason, and in general you should not access the member in a different branch of the hierarchy, as you might break the invariants on which other objects depend. Consider a type that performs an expensive calculation on some large data member (protected) and two derived types that caches the result following different strategies:

class base {
protected:
   LargeData data;
// ...
public:
   virtual int result() const;      // expensive calculation
   virtual void modify();           // modifies data
};
class cache_on_read : base {
private:
   mutable bool cached;
   mutable int cache_value;
// ...
   virtual int result() const {
       if (cached) return cache_value;
       cache_value = base::result();
       cached = true;
   }
   virtual void modify() {
       cached = false;
       base::modify();
   }
};
class cache_on_write : base {
   int result_value;
   virtual int result() const {
      return result_value;
   }
   virtual void modify() {
      base::modify();
      result_value = base::result(); 
   }
};

The cache_on_read type captures modifications to the data and marks the result as invalid, so that the next read of the value recalculates. This is a good approach if the number of writes is relatively high, as we only perform the calculation on demand (i.e. multiple modifies will not trigger recalculations). The cache_on_write precalculates the result upfront, which might be a good strategy if the number of writes is small, and you want deterministic costs for the read (think low latency on reads).

Now, back to the original problem. Both cache strategies maintain a stricter set of invariants than the base. In the first case, the extra invariant is that cached is true only if data has not been modified after the last read. In the second case, the extra invariant is that result_value is the value of the operation at all times.

If a third derived type took a reference to a base and accessed data to write (if protected allowed it to), then it would break with the invariants of the derived types.

That being said, the specification of the language is broken (personal opinion) as it leaves a backdoor to achieve that particular result. In particular, if you create a pointer to member of a member from a base in a derived type, access is checked in derived, but the returned pointer is a pointer to member of base, which can be applied to any base object:

class base {
protected:
   int x;
};
struct derived : base {
   static void modify( base& b ) {
      // b.x = 5;                        // error!
      b.*(&derived::x) = 5;              // allowed ?!?!?!
   }
}

Solution 4

In both examples Foo inherits a protected method fooBase. However, in your first example you try to access the given protected method from the same class (Foo::foo calls Foo::fooBase), while in the second example you try to access a protected method from another class which isn't declared as friend class (Foo::foo tries to call FooBase::fooBase, which fails, the later is protected).

Solution 5

In the first example you pass an object of type Foo, which obviously inherits the method fooBase() and so is able to call it. In the second example you are trying to call a protected function, simply so, regardless in which context you can't call a protected function from a class instance where its declared so. In the first example you inherit the protected method fooBase, and so you have the right to call it WITHIN Foo context

Share:
26,942
Kaiserludi
Author by

Kaiserludi

Updated on July 01, 2021

Comments

  • Kaiserludi
    Kaiserludi almost 3 years

    Why does this compile:

    class FooBase
    {
    protected:
        void fooBase(void);
    };
    
    class Foo : public FooBase
    {
    public:
        void foo(Foo& fooBar)
        {
            fooBar.fooBase();
        }
    };
    

    but this does not?

    class FooBase
    {
    protected:
        void fooBase(void);
    };
    
    class Foo : public FooBase
    {
    public:
        void foo(FooBase& fooBar)
        {
            fooBar.fooBase();
        }
    };
    

    On the one hand C++ grants access to private/protected members for all instances of that class, but on the other hand it does not grant access to protected members of a base class for all instances of a subclass. This looks rather inconsistent to me.

    I have tested compiling with VC++ and with ideone.com and both compile the first but not the second code snippet.

  • Kaiserludi
    Kaiserludi almost 12 years
    Ah, with your example code the reason, why it isn't allowed, is pretty obvious. Thanks.
  • Stefano Falasca
    Stefano Falasca about 10 years
    you are allowed to pick your own as well as your son's pockets
  • Vincent Fourmond
    Vincent Fourmond over 8 years
    I don't get your exemple. If fooBase is not virtual, what you get is FooBase::fooBase, which is what you indicate anyway. If fooBase is virtual, you're in fact calling FooSibling::fooBase, but it's also the reason why you're using virtual functions: to be able to adapt the functions to the actual object ? I don't see when this behavious is a problem.
  • Rob Kennedy
    Rob Kennedy over 8 years
    Virtual is irrelevant here, @Vincent. A member's virtual status doesn't affect who's allowed to use its name. The member's visibility is what determines who can use its name. Foo can see protected members of other known Foo objects. It cannot see protected members of any other object, not even those related to its ancestor classes, because those ancestors aren't necessary known to be Foo. Virtuality and visibility are orthogonal concepts in C++ (but not necessarily in other languages).
  • Vincent Fourmond
    Vincent Fourmond over 8 years
    @RobKennedy I understand that, I just can't see any real example in which one would need to prevent a derived class from accessing a protected member/function from another instance of the base class, be it virtual or not,.