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;