Can we increase the re-usability for this key-oriented access-protection pattern:
class SomeKey {
friend class Foo;
// more friends... ?
SomeKey() {}
// possibly non-copyable too
};
class Bar {
public:
void protectedMethod(SomeKey); // only friends of SomeKey have access
};
To avoid continued misunderstandings, this pattern is different from the Attorney-Client idiom:
- It can be more concise than Attorney-Client (as it doesn't involve proxying through a third class)
- It can allow delegation of access rights
- ... but its also more intrusive on the original class (one dummy parameter per method)
(A side-discussion developed in this question, thus i'm opening this question.)
I've read a lot of comments about non-copyability. Many people thought it should not be non copyable because then we cannot pass it as an argument to the function that needs the key. And some were even surprised it was working. Well it really should not and is apparently related to some Visual C++ compilers, as I had the same weirdness before but not with Visual C++12 (Studio 2013) anymore.
But here's the thing, we can enhance the security with "basic" non-copyability. Boost version is too much as it completely prevents use of the copy constructor and thus is a bit too much for what we need. What we need is actually making the copy constructor private but not without an implementation. Of course the implementation will be empty, but it must exists. I've recently asked who was calling the copy-ctor in such a case (in this case who calls the copy constructor of
SomeKey
when callingProtectedMethod
). The answer was that apparently the standard ensure it is the method caller that calls the-ctor
which honestly looks quite logical. So by making thecopy-ctor
private we allow friends function (theprotected
Bar
and thegranted
Foo
) to call it, thus allowingFoo
to call theProtectedMethod
because it uses value argument passing, but it also prevents anyone out ofFoo
's scope.By doing this, even if a fellow developer tries to play smart with the code, he will actually have to make
Foo
do the job, another class won't be able to get the key, and chances are he will realize his mistakes almost 100% of the time this way (hopefully, otherwise he's too much of a beginner to use this pattern or he should stop development :P ).I like this idiom, and it has the potential to become much cleaner and more expressive.
In standard C++03, I think the following way is the easiest to use and most generic. (Not too much of an improvement, though. Mostly saves on repeating yourself.) Because template parameters cannot be friends, we have to use a macro to define passkey's:
This method has two drawbacks: 1) the caller has to know the specific passkey it needs to create. While a simple naming scheme (
function_key
) basically eliminates it, it could still be one abstraction cleaner (and easier). 2) While it's not very difficult to use the macro can be seen as a bit ugly, requiring a block of passkey-definitions. However, improvements to these drawbacks cannot be made in C++03.In C++0x, the idiom can reach its simplest and most expressive form. This is due to both variadic templates and allowing template parameters to be friends. (Note that MSVC pre-2010 allows template friend specifiers as an extension; therefore one can simulate this solution):
Note with just the boilerplate code, in most cases (all non-function cases!) nothing more ever needs to be specially defined. This code generically and simply implements the idiom for any combination of classes and functions.
The caller doesn't need to try to create or remember a passkey specific to the function. Rather, each class now has its own unique passkey and the function simply chooses which passkey's it will allow in the template parameters of the passkey parameter (no extra definitions required); this eliminates both drawbacks. The caller just creates its own passkey and calls with that, and doesn't need to worry about anything else.
Great answer from @GManNickG. Learnt a lot. In trying to get it to work, found a couple of typos. Full example repeated for clarity. My example borrows "contains Key in Keys..." function from Check if C++0x parameter pack contains a type posted by @snk_kid.