clean C++ granular friend equivalent? (Answer: Attorney-Client Idiom)

There is a very simple pattern, which has retro-actively been dubbed PassKey, and which is very easy in C++11:

template <typename T>
class Key { friend T; Key() {} Key(Key const&) {} };

And with that:

class Foo;

class Bar { public: void special(int a, Key<Foo>); };

And the call site, in any Foo method, looks like:

Bar().special(1, {});

Note: if you are stuck in C++03, skip to the end of the post.

The code is deceptively simple, it embeds a few key points that are worth elaborating.

The crux of the pattern is that:

  • calling Bar::special requires copying a Key<Foo> in the context of the caller
  • only Foo can construct or copy a Key<Foo>

It is notable that:

  • classes derived from Foo cannot construct or copy Key<Foo> because friendship is not transitive
  • Foo itself cannot hand down a Key<Foo> for anyone to call Bar::special because calling it requires not just holding on to an instance, but making a copy

Because C++ is C++, there are a few gotchas to avoid:

  • the copy constructor has to be user-defined, otherwise it is public by default
  • the default constructor has to be user-defined, otherwise it is public by default
  • the default constructor has to be manually defined, because = default would allow aggregate initialization to bypass the manual user-defined default constructor (and thus allow any type to get an instance)

This is subtle enough that, for once, I advise you to copy/paste the above definition of Key verbatim rather than attempting to reproduce it from memory.


A variation allowing delegation:

class Bar { public: void special(int a, Key<Foo> const&); };

In this variant, anyone having an instance of Key<Foo> can call Bar::special, so even though only Foo can create a Key<Foo>, it can then disseminate the credentials to trusted lieutenants.

In this variant, to avoid a rogue lieutenant leaking the key, it is possible to delete the copy constructor entirely, which allows tying the key lifetime to a particular lexical scope.


And in C++03?

Well, the idea is similar, except that friend T; is not a thing, so one has to create a new key type for each holder:

class KeyFoo { friend class Foo; KeyFoo () {} KeyFoo (KeyFoo const&) {} };

class Bar { public: void special(int a, KeyFoo); };

The pattern is repetitive enough that it might be worth a macro to avoid typos.

Aggregate initialization is not an issue, but then again the = default syntax is not available either.


Special thanks to people who helped improving this answer over the years:

  • Luc Touraille, for pointing to me in the comments that class KeyFoo: boost::noncopyable { friend class Foo; KeyFoo() {} }; completely disables the copy constructor and thus only works in the delegation variant (preventing storing instance).
  • K-ballo, for pointing out how C++11 improved the situation with friend T;

Leave a Comment