这种基于密钥的访问保护模式是一个已知的习惯用语吗?

56

Matthieu M.这个答案中提出了一种访问保护模式,我以前见过,但从未有意识地考虑过这种模式:

class SomeKey { 
    friend class Foo;
    SomeKey() {} 
    // possibly make it non-copyable too
};

class Bar {
public:
    void protectedMethod(SomeKey);
};

只有 key 类的 friend 才能访问 protectedMethod() 方法:

class Foo {
    void do_stuff(Bar& b) { 
        b.protectedMethod(SomeKey()); // fine, Foo is friend of SomeKey
    }
};

class Baz {
    void do_stuff(Bar& b) {
        b.protectedMethod(SomeKey()); // error, SomeKey::SomeKey() is private
    }
};

它比将Foo作为Barfriend或使用更复杂的代理模式更能实现细粒度的访问控制。

请问是否已经有一个名称来描述这种方法,即是否是已知的模式?


1
除非你想让 Foo 能够委托其他类访问该键(当然,根据情况,委托可能是有用的),否则使键不可复制可能是有用的。 - James McNellis
1
问:以下黑客攻击是否可被击败?char* some_junk_object=new char[...]; SomeKey* p=(SomeKey*)(some_junk_object); b.protectedMethod(*p); 如果你所做的只是依赖于类型系统,那么这很容易被破坏。(我曾经看到过类似的密钥系统以类似的方式失败了。) - Owen S.
1
@Owen:如果复制构造函数是可访问的,那么是的——但是在C++中无法完全防范所有情况,总有一些方法可以绕过它。我们想要防止的是错误,而不是故意滥用。如果我们不想要委托并使复制构造函数非公开,你的方法就行不通了。 - Georg Fritzsche
这个习语是否有优化为GCC的no-op的趣闻轶事? - Emile Cormier
精彩的习语。谢谢! - Jan Korous
显示剩余10条评论
4个回答

16

感谢你的其他问题,现在这种模式被称为“passkey”模式。

在C++11中,它变得更加简洁,因为不需要调用

b.protectedMethod(SomeKey());

您只需要调用:

b.protectedMethod({});

不确定,但如果没有C++11,可以提供一个SomeKey构造函数,例如采用int,以允许较短的b.protectedMethod(0); - 463035818_is_not_a_number
只是一个细节:缩写版本 foo.bar({}) 不能与构造函数一起使用,例如 Foo foo({}),因为它是模棱两可的 - 编译器不知道调用哪个构造函数,可能是复制构造函数。或者至少这是一个看起来合理的解释,如果我错了,请纠正我。 - egst
有人能解释一下 b.protectedMethod({}) 中的 {} 是什么意思吗? - yakoda
@yako:默认构造函数的第一个参数为{} - Marco Kinski

7

看起来这个成语与另一个SO问题中提到的类似这里。它被称为律师-客户习语,并在那里有更详细的描述。


2
我已经链接到另一个回答了那个问题,上面的模式不涉及通过另一个类进行代理。我们没有需要通过“代理人”(他们也很昂贵)来调用的东西,相反,我们只需通过传递一个“密钥”来获取访问权限。 - Georg Fritzsche
@Georg:律师调用在哪些真正意义上是昂贵的?如果它们是无状态的、静态的内联函数,仅转发参数,那么任何大小或时间惩罚会如何引入? - Jeff
1
@jeff:关于费用的那个点是一个关于小时费率的双关语。不过,在律师案件中,你仍然需要手动转发 - 你需要写更多。 - Georg Fritzsche
@Georg:抱歉,我将“双关语”理解为_call_费用而不是方法定义上。我基本同意你的观点,但我在其他问题的线程中发表了有关实际运行时费用的评论。 - Jeff

3

像我这样无聊的人会写出以下代码:

int FraudKey=0;
b.protectedMethod(reinterpret_cast<SomeKey&>(FraudKey));

3
在C++中,这并不令人惊讶——它并不旨在防止恶意行为。 - Georg Fritzsche
也许将“SomeKey”设置为“Bar”的内部私有属性,可以在某种程度上保护它免受这种使用的影响 - 或者至少将其限制在“Bar”类中。但是这将要求始终使用“protectedMethod({})”。 - Tarc
2
您已经调用了未定义的行为。该错误存在于您的代码中,作为函数的调用者,因为您正在使用reinterpret_cast来访问一个对象,而该对象的类型与其实际类型不同。从未定义的行为中,会产生荒谬的结果。 - David Stone
1
这就是为什么SomeKey的复制构造函数也必须是私有的。这确保编译失败,而有效的使用将成功。 - monkey0506

0

它非常接近这个:

http://minorfs.wordpress.com/2013/01/18/raiicap-pattern-injected-singleton-alternative-for-c/

基本上,如果您认为对精心设计的类的对象的引用提供了所需的访问控制,以实现任何有意义的访问控制策略,那么将此模式应用于除构造函数之外的任何内容似乎都没有太多意义。

因此,正如文章所述,如果您将此密钥与那些构造函数一起使用,以实现可能有意义的访问控制,表示重要资源的对象,在C++中通常会作为RAII对象实现,那么RAIICap或RAII-Capability的名称确实是有意义的。

http://www.eros-os.org/essays/capintro.html

或者你可以用一个更一般的名称,比如构造授权来引用它。

文章中的实现过于以 main 为中心,也就是说,main 需要创建所有的授权密钥。您可以通过为密钥本身添加一个额外的公共构造函数来扩展它,并使其更加灵活:

template <typename T>
class construct_authority {
  public:
    construct_authority(construct_authority<void> const&)
    friend int main(int,char **);
  private:
    construct_authority(){}
};

这样一来,主函数就可以将密钥创建委托给程序的其他部分。

个人认为RAIICap名称非常适用于此模式的有用部分。

不久前,我提出将上述简单模板添加到标准库中。

https://groups.google.com/a/isocpp.org/forum/#!topic/std-proposals/p_v-aYIvO1E

不幸的是,存在一个构成计算根的主要指纹的想法存在问题,因此像这样的东西显然不能在标准库中使用。尽管如此,至少对于RAII类的构造函数使用来说,这种模式似乎非常有用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接