As it turns out, you can access the protected members of any class by taking a member pointer and then applying it to an object of that class. The example below seems to be well-formed according to 11.2 [class.access.base]/5, and to the additional rules of 11.5 [class.protected]/1, even though there is no object of a derived class involved at all.
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 ]
> As it turns out, you can access the protected members of any class by > taking a member pointer and then applying it to an object of that > class. The example below seems to be well-formed according to 11.2 > [class.access.base]/5, and to the additional rules of 11.5 > [class.protected]/1, even though there is no object of a derived class > involved at all.
> 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;
> }
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
-- [ 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 ]
litb wrote: > As it turns out, you can access the protected members of any class > by taking a member pointer and then applying it to an object of that > class. The example below seems to be well-formed according to 11.2 > [class.access.base]/5, and to the additional rules of 11.5 > [class.protected]/1, even though there is no object of a derived > class involved at all.
> 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.
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
-- [ 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 ]
> As it turns out, you can access the protected members of any class by > taking a member pointer and then applying it to an object of that > class. The example below seems to be well-formed according to 11.2 > [class.access.base]/5, and to the additional rules of 11.5 > [class.protected]/1, even though there is no object of a derived class > involved at all.
> 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.
] This is just one of the problems with inheritance - There is no way you can prevent your children from selling the familly jewels. Usually I would expect it to be a bad design that used protected data - not so much because of the access issues but because it means that you are no longer in control of class invariants and you can never again change the implementation to one that doesn't use 'm'. If you really need access to whatever 'm' is supposed to represent then it should be via protected access methods.
-- [ 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 ]
> litb wrote: >> As it turns out, you can access the protected members of any class >> by taking a member pointer and then applying it to an object of that >> class.
...
>> 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
-- [ 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 ]
> On Jun 25, 3:26 pm, litb <Schaub-Johan...@web.de> wrote:
> > As it turns out, you can access the protected members of any class by > > taking a member pointer and then applying it to an object of that > > class. The example below seems to be well-formed according to 11.2 > > [class.access.base]/5, and to the additional rules of 11.5 > > [class.protected]/1, even though there is no object of a derived class > > involved at all.
> > 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;
> > }
> 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.
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"
-- [ 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 ]
Lars Tetzlaff wrote: > Bo Persson schrieb: >> litb wrote: >>> As it turns out, you can access the protected members of any class >>> by taking a member pointer and then applying it to an object of >>> that class.
> ...
>>> 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.
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
-- [ 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 ]
> 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.
-- [ 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 ]
> 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 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!
[ 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 ]
On Jun 27, 5:06 pm, litb <Schaub-Johan...@web.de> wrote:
> 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).
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
-- [ 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 ]
> litb wrote: >> As it turns out, you can access the protected members of any class >> by taking a member pointer and then applying it to an object of that >> class. The example below seems to be well-formed according to 11.2 >> [class.access.base]/5, and to the additional rules of 11.5 >> [class.protected]/1, even though there is no object of a derived >> class involved at all.
>> 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.
> 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.
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.
-- 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 ] [ 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 ]
On Jun 29, 1:41 am, Greg Herlihy <gre...@mac.com> wrote:
> On Jun 27, 5:06 pm, litb <Schaub-Johan...@web.de> wrote:
> > 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).
> 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..
How does this apply to my statement that this DR concerns circumventing "protected" without UB? Undefined behavior can happen at compile time. Think of unbound template instantiations, non-empty file without newline at the end, ... . Undefined behavior means your implementation can do anything. The compiler is part of that.
> > 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).
-- [ 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 ]
> On Jun 27, 5:06 pm, litb <Schaub-Johan...@web.de> wrote:
>> // 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.
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 ] [ 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 ]
> > 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 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 think you are right. And when we pass &Y::m to a template, that invokes the pointer on a vector of X*, then we could still cast up manually.
> > 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?
-- [ 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 ]