Tuesday, October 9, 2007

Friends Can Take Things a Little Too Far

When I learned C++, I had a distaste for using the friend construct. It seemed troubling and broke encapsulation. As I continued on, I came to understand that friends were useful when used to make private parts of a class which otherwise would need to be public.

Another way of looking at friends, is that it is a way to compensate for shortcomings of C++ itself. At certain times we are forced to express what is actually a single entity as multiple classes. These friend relationships gives the befriended class (one way) access to the befriending class.

Of course there is the cute saying about friends being able to see your private parts...

Anyway, the friend relationship can take things a bit too far. C++ allows us to declare a class or a method as the friend of a class, giving the friend visibility to the whole class. What if this was too much exposure? What if the friend wasn't really a full friend, but something less?

For example, For class C I wish to have only one class F be able to call method m(). We can make m() private and make F a friend of C:

class C {
private:
friend class F;
void m();
static void s_m();
void n();
};

F then can call m(), s_m() and n() on C, which is too much exposure. Ideally the language would allow friendship on a level more granular than the class, but it doesn't. However we can achieve it anyway. This less than a friend relationship can be expressed through a mixture of nested classes and friend expressions. I call it the Cohort. In order to make m() the only visible function in C by F, take the following steps:
  1. Make m() private, as in the above example.
  2. Create a public nested class named Cohort within C.
  3. Add a private static function called m() within C::Cohort.
  4. Make Cohort a friend of C.
  5. Make F a friend of C::Cohort.
The example now looks like this -


class C {
public:
class Cohort {
private:
friend class F;
static void s_m() { C::s_m(); }
static void m(C* instance) { instance->m();
};

private:
friend class Cohort;

void m();
static void s_m();
void n();
};


Cohort, under full control of the class C developer, is the only friend of C, and is the only way F can call m() or s_m(). This allows the access you need and eliminates any unnecessary access to F. F must use the Cohort syntax.

C::Cohort::s_m() or C::Cohort::s(myCinstance);

Happy coding.

No comments: