我们可以增加可重用性对于这种导向键访问保护模式 :
class SomeKey {
friend class Foo;
// more friends... ?
SomeKey() {}
// possibly non-copyable too
};
class Bar {
public:
void protectedMethod(SomeKey); // only friends of SomeKey have access
};
为了避免持续的误解,这种模式是从不同的律师-客户成语:
- 它可以比律师 - 客户更简洁(因为它不通过第三类包括代理)
- 它可以允许的访问权限代表团
- ...但它也对原始类更具侵入性(一个伪每个方法参数)
(A侧的讨论中开发了这个问题 ,从而即时打开这个问题。)
我喜欢这个成语,它有可能成为更清洁,更具有表现力的潜力。
在标准C ++ 03,我觉得有以下方法是最容易使用和最通用的。 (没有太多的改善,虽然天色节省了重复自己。)因为模板参数不能成为朋友 ,我们必须使用宏来定义密钥的:
// 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发。
在C ++ 0x中,成语可以达到它的最简单,最表现形式。 这是由于两个可变参数模板,并允许模板参数成为朋友。 (请注意,MSVC预2010允许模板朋友说明符作为一个扩展;因此一个能够模拟这种解决方案):
// 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(){}
注意只用样板代码,在大多数情况下( 所有非功能的情况下!)没有以往更需要特别定义。 此代码一般地且简单地实现用于类和功能的任何组合的习语。
来电者并不需要尝试创建或记住一个密钥具体到功能。 相反,每个类现在有自己独特的口令且功能简单地选择其中的密钥的它将使在密钥参数(不需要额外的定义)的模板参数; 这消除了两者的缺点。 呼叫者只需创建自己的密钥,然后与调用,而不必担心别的。
我读过很多关于非复制性意见。 很多人认为它不应该是不能够复制,因为那时,我们不能把它作为参数传递给需要的关键功能。 而有些甚至惊奇地发现这是工作。 那么它真的不应该而且显然与一些的Visual C ++编译器,正如我前面而不是用Visual C ++ 12(工作室2013年)再有同样的怪事。
但这里的东西,我们可以加强与“基本”非复制能力的安全性。 升压版本实在是太多了,因为它完全避免使用拷贝构造函数,因此是一个有点吃不消了我们所需要的。 我们需要的是实际进行拷贝构造函数私有,但并非没有实现。 当然,实施将是空的,但它必须存在。 我最近询问是谁在呼唤在这种情况下,拷贝构造函数(在这种情况下,谁调用的拷贝构造函数SomeKey
打电话时ProtectedMethod
)。 答案是,显然标准确保它是调用方法调用者-ctor
其诚实看起来很合乎逻辑的。 因此,通过使copy-ctor
私有,我们允许好友功能( protected
Bar
和granted
Foo
)来调用它,从而使Foo
调用ProtectedMethod
,因为它使用值参数传递,而且可以阻止任何人离开Foo
的范围。
通过这样做,即使一个老乡开发商试图打得更聪明的代码,他实际上将不得不作出Foo
做的工作,另一个类将无法得到密钥,没准他几乎认识自己的错误100 %的时间这样(希望,否则他是太多初学者的使用这个模式,否则他应该停止发展:P)。
从@GManNickG伟大的答案。 学到了很多东西。 在试图得到它的工作,发现一对夫妇错别字。 完整的例子重复清晰。 我的例子借用“包含按键按键......”从功能检查的C ++ 0x参数包包含一个类型张贴@snk_kid。
#include<type_traits>
#include<iostream>
// identify if type is in a parameter pack or not
template < typename Tp, typename... List >
struct contains : std::false_type {};
template < typename Tp, typename Head, typename... Rest >
struct contains<Tp, Head, Rest...> :
std::conditional< std::is_same<Tp, Head>::value,
std::true_type,
contains<Tp, Rest...>
>::type{};
template < typename Tp >
struct contains<Tp> : std::false_type{};
// everything is private!
template <typename T>
class passkey {
private:
friend T;
passkey() {}
// noncopyable
passkey(const passkey&) = delete;
passkey& operator=(const passkey&) = delete;
};
// what keys are allowed
template <typename... Keys>
class allow {
public:
template <typename Key>
allow(const passkey<Key>&) {
static_assert(contains<Key, Keys...>::value, "Pass key is not allowed");
}
private:
// noncopyable
allow(const allow&) = delete;
allow& operator=(const allow&) = delete;
};
struct for1;
struct for2;
struct foo {
void restrict1(allow<for1>) {}
void restrict2(allow<for1, for2>){}
} foo1;
struct for1 {
void myFnc() {
foo1.restrict1(passkey<for1>());
}
};
struct for2 {
void myFnc() {
foo1.restrict2(passkey<for2>());
// foo1.restrict1(passkey<for2>()); // no passkey
}
};
void main() {
std::cout << contains<int, int>::value << std::endl;
std::cout << contains<int>::value << std::endl;
std::cout << contains<int, double, bool, unsigned int>::value << std::endl;
std::cout << contains<int, double>::value << std::endl;
}
文章来源: Can we increase the re-usability of this key-oriented access-protection pattern?