我们能否增加这种面向密钥的访问保护模式的可重用性?
Posted
技术标签:
【中文标题】我们能否增加这种面向密钥的访问保护模式的可重用性?【英文标题】:Can we increase the re-usability of this key-oriented access-protection pattern? 【发布时间】:2011-03-20 11:43:08 【问题描述】:我们能否提高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
;
为避免继续产生误解,此模式与Attorney-Client 成语不同:
它可以比 Attorney-Client 更简洁(因为它不涉及通过第三类代理) 它可以允许访问权限的委派 ...但它对原始类也更具侵入性(每个方法一个虚拟参数)(在this question 中开发的边讨论,因此我要打开这个问题。)
【问题讨论】:
假设的朋友会员资格暂且不谈无意义的 foo-bar 示例-您能否提供一个“实用”示例,其中该模式的使用优于其他一些更简单的技术,此外还有什么 C#还是 Java 等价物? @Beh:每当您必须限制对资源的访问,但又不想授予特权客户端完全访问权限(很少需要)以保留封装时。链接的律师 - 客户文章更详细。作为一个实际的例子,例如一个案例like this - 包装类不是供公众使用的,它应该是一个不透明的助手。使用它的免费函数具有完全访问权限,尽管它只需要访问包装器get_function_pointer()
。
我正在尝试将它与模板类一起使用(关键的一个朋友是模板类中的一个方法)并且由于无法分离而无法弄清楚如何管理依赖项模板声明和定义。我是否可以得出结论,我不能使用它来授予模板类方法密钥访问权限?
我决定通过创建一个将模板类包装在引用中的类来解决这个问题。然后我可以将模板类转换为非模板类,拆分声明和定义,并正常进行,几乎不会引入任何额外的复杂性或额外的代码。
【参考方案1】:
来自@GManNickG 的精彩回答。学到了很多。在试图让它工作时,发现了几个错别字。为了清楚起见,重复了完整的示例。我的示例从@snk_kid 发布的Check if C++0x parameter pack contains a type 借用了“contains Key in Keys...”功能。
#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;
【讨论】:
【参考方案2】:我已经阅读了很多关于不可复制性的 cmets。许多人认为它不应该是不可复制的,因为这样我们就不能将它作为参数传递给需要密钥的函数。有些人甚至对它的工作感到惊讶。好吧,它确实不应该而且显然与某些 Visual C++ 编译器有关,因为我之前也有过同样的怪异现象,但现在不再使用 Visual C++12 (Studio 2013)。
但问题是,我们可以通过“基本”不可复制性来增强安全性。 Boost 版本太多了,因为它完全阻止了复制构造函数的使用,因此对于我们需要的东西来说有点太多了。我们需要的实际上是使复制构造函数私有,但不是没有实现。当然实现是空的,但它必须存在。我最近问过在这种情况下谁在调用 copy-ctor(在这种情况下,谁在调用 ProtectedMethod
时调用了 SomeKey
的复制构造函数)。答案是显然标准确保它是调用-ctor
的方法调用者,老实说这看起来很合乎逻辑。因此,通过将 copy-ctor
设为私有,我们允许好友函数(protected
Bar
和 granted
Foo
)调用它,从而允许 Foo
调用 ProtectedMethod
,因为它使用值参数传递,但它也会阻止任何人超出Foo
的范围。
通过这样做,即使一个开发人员尝试巧妙地使用代码,他实际上也必须让Foo
完成这项工作,另一个班级将无法获得密钥,他很有可能会以这种方式几乎 100% 地意识到自己的错误(希望如此,否则他太初学者了,无法使用这种模式,或者他应该停止开发:P)。
【讨论】:
这不是一个答案,因此不应作为一个答案发布。 好的,我该怎么办?随机浏览 *** 上的帖子,希望能得到足够的答案来让我的代表站起来,以便我发表评论?您没有阅读我为无法发表评论而道歉的第一部分;)我必须有 50 个代表才能发表评论并且不能发表评论,这很愚蠢,如果人们不能做两个中的一个事情从一开始就是回答,而不是评论=/ 如果您为此道歉,做错事并不会神奇地纠正自己。正如您所说,您可以尝试回答一些问题,直到达到 50 次。 50 次代表并不那么,所以你应该能够相对快速地实现那些;) 尽量让它成为一个答案。只是要指出,我真的没有那么多时间在 *** 上实际制作内容以增加我的代表,我认为按照许多人的要求,cmets 处理应该有一个小的变化。在这种情况下,人们可以像我一样想要引入一些新的东西,一些不是答案而是部分答案的东西,这样他们就想为 StackExchange 的发展做出贡献,但他们做不到!我希望有一天能找到一些好主意 我真的希望因为问题(我不关心这个我仍然可以通过一点时间获得 50 个代表)是随着时间的推移,问题变得更加复杂,因此答案也有一天,只有一小部分人会提出问题或回答问题,从而阻止新人真正获得任何代表。这些新来者(人类就是这样进化的)仍然可以有一个小而聪明的新鲜想法(我有时会看到),这将大大增强已知的东西【参考方案3】:我喜欢这个成语,它有可能变得更简洁、更有表现力。
在标准 C++03 中,我认为以下方式是最容易使用和最通用的。 (不过没有太大的改进。主要是节省了重复自己的时间。)因为template parameters cannot be friends,我们必须使用宏来定义密码:
// 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 pre-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()
请注意,仅使用样板代码,在大多数情况下(所有非功能情况!)不再需要专门定义。这段代码一般简单地实现了类和函数的任何组合的习惯用法。
调用者不需要尝试创建或记住特定于函数的密钥。相反,每个类现在都有自己唯一的密钥,并且该函数只需选择允许在密钥参数的模板参数中使用的密钥(不需要额外的定义);这消除了这两个缺点。调用者只需创建自己的密钥并使用该密钥进行调用,无需担心其他任何事情。
【讨论】:
我喜欢你要去的地方,但是(当然是一个但是;))现在我们又回到了为每种类型制作一个密钥(目前我还不能选择 C++0x特征)?此外,虽然您的方法还有其他优点,但我喜欢前一个版本的简单性。它不需要支持结构,并且通过审核的问题可能更少。 @Georg:确实,我认为在 C++03 中最好的方法是接受您必须手动(好吧,使用宏更容易)为每个朋友集合制作密码,然后继续使用它。我不确定您所说的评论是什么意思,但我发现 C++03 更容易,只需将实用程序的内容放入一些passkey.hpp
标题中,然后再看一遍。 :) 宏比手动操作要干净得多。不过我真的很喜欢 C++0x 版本;事实上,最后一个参数实际上可以读作“允许这个、这个和那个”,而那个类型只是说“这是我的钥匙,让我进去”是一个梦想。
没错,锁定的 C++0x 方法的可读性很好 :) 对于评论,我的意思是更保守的指导方针或代码审查者——如果我们可以按照我们想要的方式编写所有代码,它会是另一回事(主要解决这里的宏)。
@Georg:哦,我总是可以制定自己的指导方针。 :)
allow
结构非常好,我不知道 C++0x 的模板 friend
goodie(我主要还是用 C++03 编码...)而且效果非常好!以上是关于我们能否增加这种面向密钥的访问保护模式的可重用性?的主要内容,如果未能解决你的问题,请参考以下文章