问题描述
我们可以增加的可重复使用性面向钥匙的访问保护模式:
class SomeKey { friend class Foo; // more friends... ? SomeKey() {} // possibly non-copyable too }; class Bar { public: void protectedMethod(SomeKey); // only friends of SomeKey have access };
为避免持续误解,这种模式与律师 - 律师 idiom idiom:
- 它可以比律师 - 委托人更简洁(因为它不涉及在第三类中代理)
- 它可以允许访问权利委派
- ...但是它对原始类(每方法一个虚拟参数) 也更具侵入性
(这个问题,因此我正在打开这个问题.)
推荐答案
我喜欢这个成语,它有可能变得更加清洁,更具表现力.
在标准C ++ 03中,我认为以下方法是最容易使用和最通用的方法. (不过,没有太大的改进.主要节省重复自己.)因为模板参数不能成为朋友,我们必须使用宏来定义Passkey:
// 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)呼叫者必须知道其需要创建的特定Passkey.虽然简单的命名方案(function_key)基本上消除了它,但它仍然可能是一个抽象清洁剂(而且更容易). 2)虽然使用宏不是很难看作,这有点丑陋,需要一大块的Passkey定义.但是,无法在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(){}
仅使用样板代码注意,在大多数情况下( ALL 无功能案例!),再也不需要专门定义了.此代码一致,简单地实现了类和功能的任何组合的成语.
呼叫者不需要尝试创建或记住特定于该功能的Passkey.相反,每个类都有自己的独特PassKey,并且该函数仅选择PassKey允许的Passkey参数(无需额外的定义);这消除了这两个缺点.呼叫者只是创建自己的Passkey并用它来打电话,而不必担心其他任何事情.
其他推荐答案
我已经阅读了很多有关非副本的评论.许多人认为它不可能是不可复制的,因为这样我们就不能将其作为参数将其传递给需要密钥的函数.有些人甚至感到惊讶.好吧,它确实不应该并且显然与某些视觉C ++编译器有关,因为我之前的怪异性相同,但与Visual C ++ 12(Studio 2013)不再相同.
.但是,这就是我们可以通过"基本"的非副本来增强安全性. Boost版本太多了,因为它完全阻止了复制构造函数的使用,因此对于我们需要的东西来说有点太多了.我们需要的实际上是将复制构造函数私有化,但并非没有实现.当然,实施将是空的,但必须存在.我最近问过,在这种情况下,谁打电话给Copy-ctor(在这种情况下,在调用ProtectedMethod>时称SomeKey的副本构造函数).答案是,显然标准确保了调用-ctor的方法,它看起来很合乎逻辑.因此,通过制作copy-ctor私有,但这也可以防止任何人摆脱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 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 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.
其他推荐答案
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 calling ProtectedMethod). 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 the copy-ctor private we allow friends function (the protected Bar and the granted Foo) to call it, thus allowing Foo to call the ProtectedMethod because it uses value argument passing, but it also prevents anyone out of Foo'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 ).
其他推荐答案
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.
#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; }