g++ (4.7.0) gives the following error:
c:/> g++ -c pm_test.cpp
pm_test.cpp: In member function 'virtual void
pm_derived::assign_pm()':
pm_test.cpp:6: error: 'int pm_base::m' is protected
pm_test.cpp:13: error: within this context
Here is the test code:
===== pm_test.cpp =====
class pm_base {
public:
virtual void assign_pm() { pm =&pm_base::m; }
protected: // compiles if m is public
int m; // this is line 6
protected:
int pm_base::*pm;
};
class pm_derived : protected pm_base {
public:
virtual void assign_pm() { pm =&pm_base::m; } // this is line 13
};
==========
(I get a similar error from the Comeau online compiler.)
I would have expected that a derived class would have full access to
the protected members of the base class, just as it has full access to its
own protected members. But this interpretation seems not to apply to
taking a pointer-to-member of a protected member of the base class.
An instance of a derived class does have full access to the public and
protected members of any accessible base class part of itself.
The ideal, to prevent "hacks" of classes by deriving from some common
base, would be that an instance of a derived class could not go beyond
the access outlined above, e.g. that it could not access protected parts
of a Base& (even though it can access such parts of itself or other
instances of its own class or some derived class).
For example,
<code>
class Base
{
protected:
int b_;
};
class Derived
: public Base
{
public:
static int& bOfDerived( Derived& derivedObject )
{
return derivedObject.b_; // OK.
}
static int& bOfBase( Base& baseObject )
{
return baseObject.b_; // !Nyet
}
};
int main() {}
</code>
Self test: if the rule above didn't exist, how could you easily
/inadvertently/ access parts of someone else's class, when it shares
some common base with your class?
This rule does not prevent intentional hacking. It is only about
inadvertent (and then usually disastrous) access. As example, let's
intentionally hack into the protected member "c" of a "std::stack", by
just a little friendly static_cast'ing.
Heads-up: as you will see below (if you read on), member pointers
provide a loophole -- they're very low-level, sort of like "goto" --
where it is possible to do this without any casting, and indeed
without any conversion!
<code>
#include <iostream> // std::wcout, std::endl
#include <stack> // std::stack
#include <utility> // std::begin, std::end
using namespace std;
typedef stack<int> Stack;
typedef Stack::container_type Container;
Container const& containerOf( Stack const& st )
{
struct Hack: Stack
{
static Container const& container( Stack const& st )
{
return static_cast<Hack const&>( st ).c;
}
};
return Hack::container( st );
}
void display( Stack const& st )
{
Container const& c = containerOf( st );
for( auto it = begin( c ); it != end( c ); ++it )
{
wcout << *it << ' ';
}
wcout << endl;
}
Stack& pushedOn( Stack& st, int const v )
{
st.push( v );
return st;
}
Stack& operator<<( Stack&& st, int const v )
{ return pushedOn( st, v ); }
Stack& operator<<( Stack& st, int const v )
{ return pushedOn( st, v ); }
int main()
{
Stack const st = move( Stack() << 3 << 1 << 4 << 1 << 5 );
display( st );
display( st );
display( st );
}
</code>
The usual question prompting an explanation such as above, is how Base
(or rather, the questioner) can provide a protected Base setter method
to Derived classes, so that they can apply that setter on Base& objects?
Well, there is a difference between accessing a protected something in
an /instance/ of Base, versus accessing a protected static something
inherited directly from Base.
In the latter case, with a sound design there is no chance of
inadvertently changing something in an instance of someone else's
derived class, and also it does not open up for hacking:
<code>
#include <iostream>
using namespace std;
class Base
{
protected:
int b_;
void setB( int const v ) { b_ = v; }
static void setB( Base& o, int const v ) { o.setB( v ); }
};
class Derived
: public Base
{
public:
static void foo( Base& o )
{
setB( o, 42 ); // Static member, OK.
o.setB( 666 ); // Instance member, !Nyet.
}
};
int main() {}
</code>
The situation with ordinary pointers is the same.
However, member pointers are not ordinary pointers. They're not
addresses. They're more like what used to be called /based pointers/,
i.e. offsets -- the simplest member pointer represents an offset from
the start of an instance of the class.
It can be a bit more complicated that, for pointers to virtual methods,
and when virtual inheritance is employed, but that's the basic idea, the
basic conceptual picture.
Now, an offset to some place in a Base instance can clearly be applied
to a Derived instance and mean the same there - at least in the simplest
case, and the more complicated cases are just complicated because the
internals have to support this simple programmers' picture.
However, an offset to some place in a Derived instance can not in
general be applied to a Base instance, because generally a Base instance
need not have anything there. For it can be the offset of some member
that is introduced down in Derived. So, summing up so far, a "T
(Base::*)" or "T (Base::*)(args)" can be applied to a Derived object,
but a "T (Derived::*)" or "T (Derived::*)(args)" can not in general be
applied to Base object.
And these are exactly the restrictions that the C++ rules place on
member pointers, so that the earlier hack can be rewritten as ...
<code>
Container const& containerOf( Stack const& st )
{
struct Hack: Stack
{
static Container const& container( Stack const& st )
{
return st.*&Hack::c;
}
};
return Hack::container( st );
}
</code>
which does not involve any casting or conversions, he he.
The direct reason that this works is that
`&Hack::c` is of -- ta da! -- type `Container (Stack::*)`
because c is inherited from Stack.
Yes, believe it or not, the type of the result of the address operator
here depends on whether the argument is inherited:
<code>
#include <iostream>
#include <typeinfo>
using namespace std;
struct Base { int b; };
struct Derived: Base { double d; };
void show( char const expression[], char const value[] )
{
wcout << expression << " -> " << value << endl;
}
#define SHOW( e ) show( #e, typeid( e ).name() )
int main()
{
SHOW( &Base::b );
SHOW( &Derived::b );
SHOW( &Derived::d );
}
</code>
<output>
&Base::b -> int Base::*
&Derived::b -> int Base::*
&Derived::d -> double Derived::*
</output>
Now, as you can see this stuff is a little complicated and
counter-intuitive, well, maybe more than just a little complicated, and
as a logical consequence of the general behavior member pointers allow
you to /inadvertently/ circumvent the normal access rules, as shown in
the Stack hack.
But this means that I don't know the answer to your question.
For the member pointer your derived class is not allowed to obtain
directly, where it cannot do &Base::x, it can obtain nearly as directly
via &Derived::x. And very painlessly. So I think it's just a "language
wart", an undesirable but not very serious consequence of the rules, not
worth special casing to remove.
Cheers & hth.,
- Alf