我们能否增加这种面向密钥的访问保护模式的可重用性?

我们是否可以增加这种面向密钥的访问保护模式的可重用性:

class SomeKey { friend class Foo; // more friends... ? SomeKey() {} // possibly non-copyable too }; class Bar { public: void protectedMethod(SomeKey); // only friends of SomeKey have access }; 

为避免持续的误解,这种模式与律师 – 客户惯用法不同:

  • 它可以比律师 – 客户更简洁(因为它不涉及通过第三类代理)
  • 它可以允许访问权限的授权
  • …但它也更原始的类(每个方法一个虚拟参数)

(在这个问题上进行了一个侧面讨论,因此我打开了这个问题。)

我喜欢这个成语,它有可能变得更清洁,更有performance力。

在标准的C ++ 03中,我认为以下方法是最容易使用和最通用的。 (虽然没有太多的改进,但是大部分节省了重复的工作。)因为模板参数不能成为朋友 ,所以我们必须使用macros来定义密码:

 // 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)调用者必须知道它需要创build的特定密钥。 虽然一个简单的命名scheme( function_key )基本上消除了它,它仍然可以是一个抽象更清洁(和更容易)。 2)尽pipe使用这个macros并不是很难,但是可以看作是一个有点丑陋的地方,需要一个密钥定义块。 但是,在C ++ 03中不能改进这些缺点。


在C ++ 0x中,成语可以达到其最简单,最具performance力的forms。 这是由于可变模板和允许模板参数成为朋友。 (请注意,2010年以前的MSVC允许模板朋友说明符作为扩展名;因此可以模拟此解决scheme):

 // 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(){} 

注意,只有样板代码,在大多数情况下( 所有非function的情况下!)没有什么需要特别定义。 这个代码一般而且简单地实现了任何类和函数组合的成语。

调用者不需要尝试创build或记住特定于该函数的密钥。 相反,每个类现在都拥有自己独特的密钥,并且该函数只是简单地select在密码参数的模板参数中允许使用哪个密钥(不需要额外的定义)。 这消除了两个缺点。 调用者只需创build自己的密钥和调用,而不需要担心其他任何事情。

我读了很多关于非可复制性的评论。 许多人认为它不应该是不可复制的,因为那样我们就不能把它作为需要密钥的函数的参数。 有些甚至感到惊讶,它正在工作。 那么它真的不应该和显然有关的一些Visual C ++编译器,因为我有同样的古怪,但与Visual C ++ 12(Studio 2013)不再。

但是事情就是这样,我们可以通过“基本的”非可复制性来增强安全性。 Boost版本太多了,因为它完全阻止了复制构造函数的使用,因此对于我们所需要的太多了。 我们需要的是实际上使复制构造函数私有,但不是没有实现。 当然实施将是空的,但它必须存在。 我最近在这种情况下询问了谁在调用Copy-ctor(在这种情况下调用ProtectedMethod时调用了SomeKey的复制构造函数)。 答案显然是标准确保调用方法调用者真正看起来很合乎逻辑。 因此,通过将copy-ctor private,我们允许friends函数( protected Bargranted Foo )调用它,从而允许Foo调用ProtectedMethod因为它使用值parameter passing,但是它也阻止了任何超出Foo范围的人。

通过这样做,即使一个开发人员试图在代码中玩弄聪明,他实际上也必须让Foo做这个工作,另外一个class级将不能得到钥匙,而且他很可能会发现自己的错误将近100这种方式的时间百分比(希望,否则他是太多的初学者使用这种模式,或者他应该停止发展:P)。

来自@GManNickG的伟大答案。 学到了很多。 试图让它起作用,发现了一些错别字。 为了清晰起见,重复举例。 我的例子borrows“包含Key in Keys …”函数检查C ++ 0x参数包是否包含由@snk_kid发布的types。

 #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; }