我们能否提高这种面向密钥的访问保护模式的可重用性?
我们可以提高this面向密钥的访问保护模式:
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:
- 它可以比律师-客户更简洁(因为它不涉及通过第三类代理)
- 它可以允许访问权限的委派
- ...但它对原始类也更具侵入性(每个方法一个虚拟参数)
(在这个问题,所以我要打开这个问题.)
(A side-discussion developed in this question, thus i'm opening this question.)
推荐答案
我喜欢这个习语,它有可能变得更简洁、更具表现力.
I like this idiom, and it has the potential to become much cleaner and more expressive.
在标准 C++03 中,我认为以下方式是最容易使用和最通用的.(不过,并没有太大的改进.主要是节省重复自己.)因为 模板参数不能是朋友,我们必须使用宏来定义密码:
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(){}
这种方法有两个缺点:1)调用者必须知道它需要创建的特定密码.虽然一个简单的命名方案 (function_key
) 基本上消除了它,但它仍然可以是一个更清晰的抽象(并且更容易).2)虽然使用起来不是很困难,但宏可以被视为有点丑陋,需要一个密码定义块.但是,在 C++03 中无法对这些缺点进行改进.
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.
在 C++0x 中,习语可以达到最简单、最具表现力的形式.这是由于可变参数模板和允许模板参数成为朋友.(请注意,MSVC 2010 之前的版本允许模板好友说明符作为扩展;因此可以模拟此解决方案):
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.
相关文章