Is This Key-Oriented Access-Protection Pattern a Known Idiom

Is this key-oriented access-protection pattern a known idiom?

Thanks to your other question it looks like this pattern is now known as the "passkey" pattern.

In C++11, it gets even cleaner, because instead of calling

b.protectedMethod(SomeKey());

you can just call:

b.protectedMethod({});

Can we increase the re-usability of this key-oriented access-protection pattern?

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:

// define passkey groups
#define EXPAND(pX) pX

#define PASSKEY_1(pKeyname, pFriend1) \
class EXPAND(pKeyname) \
{ \
private: \
friend EXPAND(pFriend1); \
EXPAND(pKeyname)() {} \
\
EXPAND(pKeyname)(const EXPAND(pKeyname)&); \
EXPAND(pKeyname)& operator=(const EXPAND(pKeyname)&); \
}

#define PASSKEY_2(pKeyname, pFriend1, pFriend2) \
class EXPAND(pKeyname) \
{ \
private: \
friend EXPAND(pFriend1); \
friend EXPAND(pFriend2); \
EXPAND(pKeyname)() {} \
\
EXPAND(pKeyname)(const EXPAND(pKeyname)&); \
EXPAND(pKeyname)& operator=(const EXPAND(pKeyname)&); \
}
// and so on to some N

//////////////////////////////////////////////////////////
// test!
//////////////////////////////////////////////////////////
struct bar;
struct baz;
struct qux;
void quux(int, double);

struct foo
{
PASSKEY_1(restricted1_key, struct bar);
PASSKEY_2(restricted2_key, struct bar, struct baz);
PASSKEY_1(restricted3_key, void quux(int, double));

void restricted1(restricted1_key) {}
void restricted2(restricted2_key) {}
void restricted3(restricted3_key) {}
} f;

struct bar
{
void run(void)
{
// passkey works
f.restricted1(foo::restricted1_key());
f.restricted2(foo::restricted2_key());
}
};

struct baz
{
void run(void)
{
// cannot create passkey
/* f.restricted1(foo::restricted1_key()); */

// passkey works
f.restricted2(foo::restricted2_key());
}
};

struct qux
{
void run(void)
{
// cannot create any required passkeys
/* f.restricted1(foo::restricted1_key()); */
/* f.restricted2(foo::restricted2_key()); */
}
};

void quux(int, double)
{
// passkey words
f.restricted3(foo::restricted3_key());
}

void corge(void)
{
// cannot use quux's passkey
/* f.restricted3(foo::restricted3_key()); */
}

int main(){}

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):

// each class has its own unique key only it can create
// (it will try to get friendship by "showing" its passkey)
template <typename T>
class passkey
{
private:
friend T; // C++0x, MSVC allows as extension
passkey() {}

// noncopyable
passkey(const passkey&) = delete;
passkey& operator=(const passkey&) = delete;
};

// functions still require a macro. this
// is because a friend function requires
// the entire declaration, which is not
// just a type, but a name as well. we do
// this by creating a tag and specializing
// the passkey for it, friending the function
#define EXPAND(pX) pX

// we use variadic macro parameters to allow
// functions with commas, it all gets pasted
// back together again when we friend it
#define PASSKEY_FUNCTION(pTag, pFunc, ...) \
struct EXPAND(pTag); \
\
template <> \
class passkey<EXPAND(pTag)> \
{ \
private: \
friend pFunc __VA_ARGS__; \
passkey() {} \
\
passkey(const passkey&) = delete; \
passkey& operator=(const passkey&) = delete; \
}

// meta function determines if a type
// is contained in a parameter pack
template<typename T, typename... List>
struct is_contained : std::false_type {};

template<typename T, typename... List>
struct is_contained<T, T, List...> : std::true_type {};

template<typename T, typename Head, typename... List>
struct is_contained<T, Head, List...> : is_contained<T, List...> {};

// this class can only be created with allowed passkeys
template <typename... Keys>
class allow
{
public:
// check if passkey is allowed
template <typename Key>
allow(const passkey<Key>&)
{
static_assert(is_contained<Key, Keys>::value,
"Passkey is not allowed.");
}

private:
// noncopyable
allow(const allow&) = delete;
allow& operator=(const allow&) = delete;
};

//////////////////////////////////////////////////////////
// test!
//////////////////////////////////////////////////////////
struct bar;
struct baz;
struct qux;
void quux(int, double);

// make a passkey for quux function
PASSKEY_FUNCTION(quux_tag, void quux(int, double));

struct foo
{
void restricted1(allow<bar>) {}
void restricted2(allow<bar, baz>) {}
void restricted3(allow<quux_tag>) {}
} f;

struct bar
{
void run(void)
{
// passkey works
f.restricted1(passkey<bar>());
f.restricted2(passkey<bar>());
}
};

struct baz
{
void run(void)
{
// passkey does not work
/* f.restricted1(passkey<baz>()); */

// passkey works
f.restricted2(passkey<baz>());
}
};

struct qux
{
void run(void)
{
// own passkey does not work,
// cannot create any required passkeys
/* f.restricted1(passkey<qux>()); */
/* f.restricted2(passkey<qux>()); */
/* f.restricted1(passkey<bar>()); */
/* f.restricted2(passkey<baz>()); */
}
};

void quux(int, double)
{
// passkey words
f.restricted3(passkey<quux_tag>());
}

void corge(void)
{
// cannot use quux's passkey
/* f.restricted3(passkey<quux_tag>()); */
}

int main(){}

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.

How to name this key-oriented access-protection pattern?

I like, in decreasing preference:

  • passkey friend idiom
  • passkey-door friend idiom
  • pass-door friend idiom
  • key-door friend idiom
  • partial-friend idiom
  • restricted-friend idiom

I moved away from the key-lock/key-keyhole naming scheme to the pass naming scheme, which grew on me.

Make a friend class have only special access to 1 function of another class?

If there is one (or few) members functions in class A, that want to use class B's private member functions, then you can declare those one/few functions as friend. E.g.

class B {
// ...
friend void A::mutateB( B * );
// ...
};

See http://en.wikipedia.org/wiki/Friend_function

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

The Attorney-Client idiom may be what you're looking for. The mechanics are not too different from your member proxy class solution, but this way is more idiomatic.

Is there any use for a class to contain only (by default) private members in c++?

There is a pattern, used for access protection, based on that kind of class: sometimes it's called passkey pattern (see also clean C++ granular friend equivalent? (Answer: Attorney-Client Idiom) and How to name this key-oriented access-protection pattern?).

Only a friend of the key class has access to protectedMethod():

// All members set by default to private
class PassKey { friend class Foo; PassKey() {} };

class Bar
{
public:
void protectedMethod(PassKey);
};

class Foo
{
void do_stuff(Bar& b)
{
b.protectedMethod(PassKey()); // works, Foo is friend of PassKey
}
};

class Baz
{
void do_stuff(Bar& b)
{
b.protectedMethod(PassKey()); // error, PassKey() is private
}
};

Golang Inheritance and method override

You're wanting an is-a relationship (inheritance) when Go only provides has-a relationships (composition):

  • Go has no inheritance, thus there is no is-a relationship between two types. Child is not a kind of Parent, so a pointer to a Parent cannot retain a pointer to a Child; Child has-a Parent contained within it instead.

Because there is no is-a relationship between Parent and Child, Parent.Foo cannot receive an object of type Child, nor can it use any of the methods that Child implements. Also, this means that no Parent can access any method defined on a Child such as Bar() or B() directly.

Typically, Parent will not need to call some method in Child. If it does, you'd pass the Parent method an argument, such as an interface that Child satisfies for you to call the method through the interface or a closure that calls the Child method:

// Use of an interface that Child satisfies.
type Beta interface {
B()
}
func (p *Parent) A(b Beta) {
p.Lock()
defer p.Unlock()
b.B()
}

// Use of a closure.
func (p *Parent) Foo(bar func()) {
p.Lock()
defer p.Unlock()
bar()
}
func callFoo(p *Parent, c *Child) {
callBar := func() {
c.Bar()
}
p.Foo(callBar)
}

func (c *Child) Bar() {
// stub
}

func (c *Child) B() {
// stub
}

You get the Child can call Parent method behavior for free, but it only appears similar to inheritance. child.Foo() actually performs child.Parent.Foo(), which means Parent.Foo still receives a Parent instance (hence the name), not a Child instance.

However, Parent cannot access any information about Child that Child does not explicitly share. Interfaces and closures can act as mechanisms between two classes analogous to the friend keyword in C++, except they're more restrictive than the friend keyword. After all, Child doesn't need to share everything with Parent, just the bits that it wants to share, somewhat similar to this pattern in C++. Personally I would prefer interfaces for this as it allows your Parent "class" to work with multiple types that all satisfy a common interface, making it pretty much the same as calling the method from a normal function or an entirely unrelated type's method.

How to ensure a specific class only can create an instance of another class?

You could adopt the passkey pattern (borrowing from Rakete1111's example):

class pass_key { friend class allowed_t; pass_key() {} };

struct restricted_t
{
restricted_t(pass_key);
};

class allowed_t
{
public:
allowed_t() : yes(pass_key()) {} // ok, allowed_t is a friend of pass_key

private:
restricted_t yes;
};

class not_allowed_t
{
public:
not_allowed_t() : no(pass_key()) {} // not ok, pass_key() is private

private:
restricted_t no;
};

int main()
{
allowed_t a; // OK, can access constructor
not_allowed_t b; // ERROR, only a friend of the key
// class has access to the restricted
// constructor
}

The pattern allows a more fine-granular access-control than making restricted_t a friend of allowed_t and avoids complicated proxying patterns.



Related Topics



Leave a reply



Submit