struct X {
protected:
int m;
};
int& getM(X &x) {
struct Voyeur : X { using X::m; };
return x.*(&Voyeur::m);
}
int main() {
X x;
getM(x) = 0;
}
This can also be achieved by a static member function within the
derived class.
--
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
[ your news-reader. If that fails, use mailto:std...@netlab.cs.rpi.edu]
[ --- Please see the FAQ before posting. --- ]
[ FAQ: http://www.comeaucomputing.com/csc/faq.html ]
C++ access controls apply to the names of the class's data members -
and not to the data member objects themselves. Therefore, a routine
with access to a data member's name is certainly free to return a
pointer to that data member. Therefore, the above program does not
demonstrate any defect in in the language, but instead behaves as it
should.
Moreover, there is no reason to forbid pointers to protected or
private data members (since a C++ program that does not wish to have
such pointers can simply refrain from creating them, whereas a program
that did need to create pointers to protected or private data members
- would be pointlessly prevented from doing so).
Greg
I don't hink this is 'circumventing' in any way, but by design. By
making the member protected, you grant visibility to any derived
classes, and trust them to behave properly.
If you don't want this effect, you should make the variable private.
Bo Persson
...
>>
>> This can also be achieved by a static member function within the
>> derived class.
>
> I don't hink this is 'circumventing' in any way, but by design. By
> making the member protected, you grant visibility to any derived
> classes, and trust them to behave properly.
>
> If you don't want this effect, you should make the variable private.
>
>
> Bo Persson
>
>
>
But even if its's private, I can
#define private public
before including the header. Accesscontrol in C++ only is a strong hint
to the user of a class. If someone really want's to access a member it
is possible.
Lars
Some people seem to have misunderstood the DR. First the whole purpose
of the DR is that this accesses a protected member without undefined
behavior happening (as opposed to "clever" things like "#define
private public" that violate the ODR). And second, what is the purpose
of "protected", if the only difference is that instead of accessing a
member using a.f, you access the member using getF(a), with getF being
written trivially in one line of code?
Here is a better testcase by another guy. I wish my testcases would be
equally clear :)
// snip
struct X { protected: int m; };
struct Y: X {
// error: X::m is protected within this context
static int & getM_naive(X & x) { return x.m; }
// ok, no problem..
static int & getM_sneaky(X & x) { return x.*(&Y::m); }
};
// snap
In my view, the problem is that things like &Y::m yield a X::*, and do
not preserve the use of Y as the nested name specifier. The standard
then has to accept applications of it on a X, because it doesn't know
how the member pointer was taken in the first case. It's also not
feasible to make it have type X::*, because that means we cannot apply
it on things like the following:
X &x = some_Y;
// if &Y::m had type T Y::*, this would fail
x.*&Y::m = ...;
I propose to add text like the following into 5.5[expr.mptr.oper]/4:
"If the dynamic type of the object expression is not of type the same
type as the nested name specifier used to obtain the member pointer or
not of a type derived from it, then the program is ill-formed; no
diagnostic required"
In theory this make the program invalid, as #defining keywords is
explicitly forbidded (17.4.3.1.1). In practice, it probably would
work.
The C++ access control protects you against accidents, not against a
determined intruder. For example, most objects can be memcpy'd to an
array of unsigned char, giving you access to the underlying bytes. The
result isn't very portable though. :-)
Bo Persson
>
> The C++ access control protects you against accidents, not against a
> determined intruder. For example, most objects can be memcpy'd to an
> array of unsigned char, giving you access to the underlying bytes. The
> result isn't very portable though. :-)
>
>
Actually most C++ objects of class type cannot be memcpy'ed only those
that are PODs are guaranteed to behave correctly when abused this way.
I can't see why anyone would want to have that allowed without explicit casting?
After all, if you know that x is really a Y (or derived), and hardcode that,
then just declare x as a Y in the first place.
> I propose to add text like the following into 5.5[expr.mptr.oper]/4:
>
> "If the dynamic type of the object expression is not of type the same
> type as the nested name specifier used to obtain the member pointer or
> not of a type derived from it, then the program is ill-formed; no
> diagnostic required"
I can't see that adding even more UB and optional diagnostics solves anything;
compilers can already diagnose at will without any specific permission for any
particular case.
What's needed or IMO desirable is enforcement by the type rules, so that the
same access rules apply for member pointers as for other code.
And for that, �5.3.1/2 about the type of an address operator expression should
be changed.
Currently it says
'If the member is a non-static member of class C of type T, the type of the
result is "pointer to member of class C of type T"'
And one reasonable resolution is
'If the member is a non-static member of class C1 of type T, the type of the
result is "pointer to member of class C2 of type T", where C2 is the class
nominated by the qualified-id [note: C2 is the same as or derived from C1]'
and in the following example changing "has type int A::*" to "has type B::*".
Impact on existing code: explicit casting needs to be added to code that uses
the type system loophole, probably a number of bugs will be discovered. :-)
Cheers,
- Alf
--
Due to hosting requirements I need visits to <url: http://alfps.izfree.com/>.
No ads, and there is some C++ stuff! :-) Just going there is good. Linking
to it is even better! Thanks in advance!
Access controls are strictly a compile-time check - they have no
effect on a program's behavior. Note that in the example program, the
code that obtained the pointer to the data member did have access to
that protected member..
> And second, what is the purpose
> of "protected", if the only difference is that instead of accessing a
> member using a.f, you access the member using getF(a), with getF being
> written trivially in one line of code?
The difference is significant: the getF() form indicates that
accessing the protected members (of a different object) is deliberate.
After all, if a class method is ablre to access its own protected
members, then surely, it should somehow be able to access the
protected members of other instances of the same class.
However, to distinguish between accessing the protected data of the
current instance versus other instances, a class method must use a
member pointer in order to access the protected members of a different
instance. This extra step helps to ensure that the access to the
protected members is deliberate.
> Here is a better testcase by another guy. I wish my testcases would be
> equally clear :)
>
> // snip
> struct X { protected: int m; };
>
> struct Y: X {
> // error: X::m is protected within this context
> static int & getM_naive(X & x) { return x.m; }
> // ok, no problem..
> static int & getM_sneaky(X & x) { return x.*(&Y::m); }};
>
> // snap
>
> In my view, the problem is that things like &Y::m yield a X::*, and do
> not preserve the use of Y as the nested name specifier.
No, &Y::m yields a member pointer whose type is in fact "int Y::*", so
the type "Y" is encoded in the member pointer itself.
> The standard
> then has to accept applications of it on a X, because it doesn't know
> how the member pointer was taken in the first case. It's also not
> feasible to make it have type X::*, because that means we cannot apply
> it on things like the following:
>
> X &x = some_Y;
> // if &Y::m had type T Y::*, this would fail
> x.*&Y::m = ...;
x.&Y::m does fail in any context that does not have access to Y's
protected data members.
> I propose to add text like the following into 5.5[expr.mptr.oper]/4:
>
> "If the dynamic type of the object expression is not of type the same
> type as the nested name specifier used to obtain the member pointer or
> not of a type derived from it, then the program is ill-formed; no
> diagnostic required"
In C++, member pointers can be applied only to objects of the class
specified by the member pointer (or a derived class). A program that
applies a member pointer to an object of some other class is already
ill-formed - and a diagnostic is required.
Greg
Consider what's *not* allowed by the current rules:
class X
{
protected:
int m;
};
class Y: public X
{
static void foo( X const& x )
{
x.m; // !Not permitted.
}
};
If the above was permitted then you could very easily access any class'
inherited protected members simply by deriving a class from the same base class.
As I recall this is even a FAQ item in Cline's FAQ Lite (it's sort of basic),
and this very intentional access restriction is what member pointers break.
And the same rationale that disallows the above "should" ideally apply equally
to member pointer rules, but for the reason discussed below it doesn't:
class P
{
protected:
int m;
};
class Q: public P
{
static void foo( P const& x )
{
x.*(&Q::m); // Permitted...
}
};
What goes on here is perhaps not evident at a glance. For it's not the case that
the right hand side of the '.*' is of type 'int Q::*', or is an 'int Q::*'
implicitly converted to 'int P::*', as one might suspect. As a counter-example:
class Q: public P
{
static void foo( P const& x )
{
int Q::* mp = &Q::m;
x.*mp; // !Not permitted.
}
};
But this can be rewritten as
class Q: public P
{
static void foo( P const& x )
{
int P::* mp = &Q::m;
x.*mp; // Permitted...
}
};
And since there is no implicit conversion Q::* to P::* (for member pointers
there's no implicit up-cast, which is opposite of ordinary pointer rules) the
conclusion is that the expression '&Q::m' necessarily is of type 'int P::*'.
In the standard this access-rule-breaking quite unexpected type is specified by
�5.3.1/2, even with an example showing exactly that.
And the only reasonable fix IMHO is to make the type of '&Q::m' an 'int Q::*'.
> If you don't want this effect, you should make the [member] private.
No, I'm sorry, that's meaningless: see above.
Cheers & hth.,
- Alf (hoping that by now he's forgiven for that comment about this in clc++m?,
http://groups.google.com/group/comp.lang.c++.moderated/browse_thread/thread/ec22da1497adbf96)
--
Due to hosting requirements I need visits to <url: http://alfps.izfree.com/>.
No ads, and there is some C++ stuff! :-) Just going there is good. Linking
to it is even better! Thanks in advance!
> > And second, what is the purpose
> > of "protected", if the only difference is that instead of accessing a
> > member using a.f, you access the member using getF(a), with getF being
> > written trivially in one line of code?
>
> The difference is significant: the getF() form indicates that
> accessing the protected members (of a different object) is deliberate.
>
protected access control means only the derived class/friends has
access to the members of the inherited members. But as the DR shows,
you can easily go and modify std::stack's adapted deque or other
intimate implementation details. This can't be the Standard's
intention. Whether the access to the protected member looks deliberate
or not doesn't matter.
> After all, if a class method is ablre to access its own protected
> members, then surely, it should somehow be able to access the
> protected members of other instances of the same class.
>
In the example i showed, i accessed a protected member of some
arbitrary typed object. Whether i use an intermediary class to achieve
that or not shouldn't matter at all. The result should be that the
member stays protected.
> However, to distinguish between accessing the protected data of the
> current instance versus other instances, a class method must use a
> member pointer in order to access the protected members of a different
> instance. This extra step helps to ensure that the access to the
> protected members is deliberate.
>
This doesn't make sense to me. Either the access to objects not in the
same hierarchy is allowed, or it is forbidden. In this case, it is
allowed by the Standard, and it seems to be just because of a lack of
mechanism to forbid it. I don't think it's deliberately allowed with
the use of member pointers, just to make the access more difficult or
complicated.
>
>
> > Here is a better testcase by another guy. I wish my testcases would be
> > equally clear :)
>
> > // snip
> > struct X { protected: int m; };
>
> > struct Y: X {
> > // error: X::m is protected within this context
> > static int & getM_naive(X & x) { return x.m; }
> > // ok, no problem..
> > static int & getM_sneaky(X & x) { return x.*(&Y::m); }};
>
> > // snap
>
> > In my view, the problem is that things like &Y::m yield a X::*, and do
> > not preserve the use of Y as the nested name specifier.
>
> No, &Y::m yields a member pointer whose type is in fact "int Y::*", so
> the type "Y" is encoded in the member pointer itself.
>
Please check your claims before you post it to a DR discussion,
especially if the reporter claims otherwise. You are wrong, of course.
> > The standard
> > then has to accept applications of it on a X, because it doesn't know
> > how the member pointer was taken in the first case. It's also not
> > feasible to make it have type X::*, because that means we cannot apply
> > it on things like the following:
>
> > X &x = some_Y;
> > // if &Y::m had type T Y::*, this would fail
> > x.*&Y::m = ...;
>
> x.&Y::m does fail in any context that does not have access to Y's
> protected data members.
>
As the comment says, it would if &Y::m would have type T Y::*, it
doesn't with current C++ semantics.
> > I propose to add text like the following into 5.5[expr.mptr.oper]/4:
>
> > "If the dynamic type of the object expression is not of type the same
> > type as the nested name specifier used to obtain the member pointer or
> > not of a type derived from it, then the program is ill-formed; no
> > diagnostic required"
>
> In C++, member pointers can be applied only to objects of the class
> specified by the member pointer (or a derived class). A program that
> applies a member pointer to an object of some other class is already
> ill-formed - and a diagnostic is required.
>
Yes, you are right. But I didn't claim that this is allowed (in fact,
i showed a failing example).
That's wrong.
Cheers & hth.,
- Alf
--
Due to hosting requirements I need visits to <url: http://alfps.izfree.com/>.
No ads, and there is some C++ stuff! :-) Just going there is good. Linking
to it is even better! Thanks in advance!
[ comp.std.c++ is moderated. To submit articles, try just posting with ]
> > I propose to add text like the following into 5.5[expr.mptr.oper]/4:
>
> > "If the dynamic type of the object expression is not of type the same
> > type as the nested name specifier used to obtain the member pointer or
> > not of a type derived from it, then the program is ill-formed; no
> > diagnostic required"
>
> I can't see that adding even more UB and optional diagnostics solves anything;
> compilers can already diagnose at will without any specific permission for any
> particular case.
>
I wanted to draw such code invalid, disregarding from what the
compiler does. So that such stuff isn't allowed anymore. But my
proposed solution is probably a bit messy anyway, because it makes a
restriction on something not really carried over by the type system.
This would probably keep being a "theoretic ill-formed that's never
diagnosed", and i agree with you such things should be avoided if
that's what you are pointing at.
> What's needed or IMO desirable is enforcement by the type rules, so that the
> same access rules apply for member pointers as for other code.
>
> And for that, �5.3.1/2 about the type of an address operator expression should
> be changed.
>
> Currently it says
>
> 'If the member is a non-static member of class C of type T, the type of the
> result is "pointer to member of class C of type T"'
>
> And one reasonable resolution is
>
> 'If the member is a non-static member of class C1 of type T, the type of the
> result is "pointer to member of class C2 of type T", where C2 is the class
> nominated by the qualified-id [note: C2 is the same as or derived from C1]'
>
> and in the following example changing "has type int A::*" to "has type B::*".
>
> Impact on existing code: explicit casting needs to be added to code that uses
> the type system loophole, probably a number of bugs will be discovered. :-)
>
I like your solution. I don't know how much code would be broken by
this, though. Any idea why this isn't so in the Standard already?
--