Uses and Abuses of Access Rights
Difficulty: 6 / 10
Who really has access to your class's internals? This issue is about liars, cheats, pickpockets, and thieves, and how to recognize and avoid them.
Problem
JG Question
1. What code can access the following parts of a class:
a) public
b) protected
c) private
Guru Questions
2. Consider the following header file:
// File x.h
//
class X
{
public:
X() : private_(1) { /*...*/ }
template<class T>
void f( const T& t ) { /*...*/ }
int Value() { return private_; }
// ...
private:
int private_;
};
Demonstrate:
a) a non-standards-conforming and non-portable hack; and
b) a fully standards-conforming and portable technique
for any calling code to get direct access to this class's private_
member.
3. Is this a hole in C++'s access control mechanism, and therefore a hole in C++'s encapsulation? Discuss.
Solution
This issue of GotW is about liars, cheats, pickpockets, and thieves.
1. What code can access the following parts of a class:
a) public
b) protected
c) private
In short:
a) Public members can be accessed by any code.
b) Protected members can be accessed by the class's own member functions and friends, and by the member functions and friends of derived classes.
c) Private members can be accessed by the class's own member functions and friends only.
That's the usual answer, and it's true as far as it goes. In this issue of GotW, we consider a special case where the above doesn't quite hold, because C++ sometimes provides a way that makes it legal (if not moral) to subvert access to a class's private members.
2. [...] Demonstrate:
a) a non-standards-conforming and non-portable hack; and
b) a fully standards-conforming and portable technique
for any calling code to get direct access to this class's private_
member.
There's a strange and perverse fascination that makes people stare at car wrecks, underworld hit men, and evil code hackery, so we might as well begin with a visit to a tragic "hit" scene in (a) and get it out of the way.
For a non-standards-conforming and non-portable hack, several ideas come to mind. Here are three of the more infamous offenders, all probable cousins of the hit man:
Criminal #1: The Liar
The Liar's hack of choice is duplicate a forged class definition to make it say what he wants it to say. For example:
// Example 1: Lies and forgery
//
class X
{
// instead of including x.h, manually duplicates X's
// definition, and adds a line like:
friend ::Hijack( X& );
};
void Hijack( X& x )
{
x.private_ = 2; // evil laughter here
}
This man is a Liar. Mark him well, for he cannot be trusted.
This is illegal because it violates the One Definition Rule, which says that if a type (here X
) is defined more than once, the definitions must be identical. The object being used above may be called an X
and may look like an X
, but it's not the same kind of X
all the other code in the program is using.
Still, this hack will work on most compilers because usually the underlying object data layout will still be the same, and if so then the Liar may be able to get away with his lie for a time.
Criminal #2: The Pickpocket
The Pickpocket's hack of choice is to silently change the meaning of the class definition. For example:
// Example 2: Evil macro magic
//
#define private public // illegal
#include "x.h"
void Hijack( X& x )
{
x.private_ = 2; // evil laughter here
}
This man is a pickpocket. Mark him well, for his fingers are light.
The code in Example 2 is nonportable for two reasons:
a) It is illegal to #define
a reserved word.
b) It violates the One Definition Rule, as above. Still, if the object's data layout is unchanged, the hack may seem to work for a while.
Criminal #3: The Cheat
The Cheat's modus operandi is to substitute one item when you're expecting another. For example:
// Example 3: Nasty attempt to simulate the object layout
// (cross your fingers and toes).
//
class BaitAndSwitch
// hopefully has the same data layout as X
{ // so we can pass him off as one
public:
int notSoPrivate;
};
void f( X& x )
{
// evil laughter here
(reinterpret_cast<BaitAndSwitch&>(x)).notSoPrivate = 2;
}
This man is a cheat. Mark him well, for he's the kind of bait-and-switch artist who runs newspaper ads just to get you into his store, and then claims not to have the advertised item and tries to fob off something else of lesser value and higher price.
The code in Example 3 is illegal for two reasons:
a) The object layouts of X
and BaitAndSwitch
are not guaranteed to be the same, although in practice they probably always will be.
b) The results of the reinterpret_cast
are undefined, although most compilers will let you try to use the resulting reference in the way the hacker intended.
We're also asked to look for "a fully standards-conforming and portable technique." Alas, while many criminals and hackers are smelly and unwashed and nonconforming, some do conform and have an air of respectability:
Person #4: The Language Lawyer
Consider the following code:
// Example 4: The legal weasel
//
namespace
{
struct Y {};
}
template<>
void X::f( const Y& )
{
private_ = 2; // evil laughter here
}
void Test()
{
X x;
cout << x.Value() << endl; // prints 1
x.f( Y() );
cout << x.Value() << endl; // prints 2
}
This man is a language lawyer who knows the loopholes. He will never be caught, for he is careful to obey the letter of the law while pillaging its spirit. Mark and avoid such ungentlemen.
Example 4 exploits the fact that X
has a member template. The code is entirely conforming and is guaranteed by the standard to work as expected. The reason is twofold:
a) It's legal to specialize a member template on any type.
The only room for error would be if you tried to specialize it on the same type twice in different ways, which would be a One Definition Rule violation, but we get around that because:
b) The code uses a type that's guaranteed to have a unique name, because it's in the hacker's unnamed namespace. Therefore it is guaranteed to be legal and won't tromp on anyone else's specialization.
There remains only one question:
3. Is this a hole in C++'s access control mechanism, and therefore a hole in C++'s encapsulation? Discuss.
This demonstrates an interesting interaction between two C++ features: The access control model, and the template model. It turns out that member templates appear to implicitly "break encapsulation" in the sense that they effectively provide a portable way to bypass the class access control mechanism.
This isn't actually a problem. The issue here is of protecting against Murphy vs. protecting against Machiavelli... that is, protecting against accidental misuse (which the language does very well) vs. protecting against deliberate abuse (which is effectively impossible). In the end, if a programmer wants badly enough to subvert the system, he'll find a way, as demonstrated above in Examples 1 to 3.
The real answer to the issue is: Don't do that! Admittedly, even Scott Meyers has said publicly that there are times when it's tempting to have a quick way to bypass the access control mechanism temporarily, such as to produce better diagnostic output during debugging... but it's just not a habit you want to get into for production code, and it should appear on the list of "one-warning offences" in your development shop.
From the GotW coding standards:
never subvert the language; for example, never attempt to break encapsulation by copying a class definition and adding a friend declaration, or by providing a local instantiation of a template member function (GotW #76)