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.
In addition to hobo's answer you may seek a workaround.
If you want the subclasses to want to call the
fooBase
method you can make itstatic
. static protected methods are accessible by subclasses with all arguments.When
foo
receives aFooBase
reference, the compiler doesn't know whether the argument is a descendant ofFoo
, so it has to assume it's not.Foo
has access to inherited protected members of otherFoo
objects, not all other sibling classes.Consider this code:
If
Foo::foo
can call protected members of arbitraryFooBase
descendants, then it can call the protected method ofFooSibling
, which has no direct relationship toFoo
. That's not how protected access is supposed to work.If
Foo
needs access to protected members of allFooBase
objects, not just those that are also known to beFoo
descendants, thenFoo
needs to be a friend ofFooBase
: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 stateprotected
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:
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). Thecache_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
istrue
only ifdata
has not been modified after the last read. In the second case, the extra invariant is thatresult_value
is the value of the operation at all times.If a third derived type took a reference to a
base
and accesseddata
to write (ifprotected
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 ofbase
, which can be applied to anybase
object: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
I tend to see things in terms of concepts and messages. If your FooBase method was actually called "SendMessage" and Foo was "EnglishSpeakingPerson" and FooBase was SpeakingPerson, your protected declaration is intended to restrict SendMessage to between EnglishSpeakingPersons (and subclasses eg: AmericanEnglishSpeakingPerson, AustralianEnglishSpeakingPerson) . Another type FrenchSpeakingPerson derived from SpeakingPerson would not be able to receive a SendMessage, unless you declared the FrenchSpeakingPerson as a friend, where 'friend' meant that the FrenchSpeakingPerson has a special ability to receive SendMessage from EnglishSpeakingPerson (ie can understand English).
In both examples
Foo
inherits a protected methodfooBase
. However, in your first example you try to access the given protected method from the same class (Foo::foo
callsFoo::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 callFooBase::fooBase
, which fails, the later is protected).