Jef*_*eff 47 c++ design-patterns private friend
为什么C++有public成员,任何人都可以调用和friend声明所有 private成员公开给定的外部类或方法,但没有提供语法将特定成员暴露给给定的调用者?
我想表达一些例程的接口,这些例程只能由已知的调用者调用,而不必让那些调用者完全访问所有的私有,这感觉是一个合理的想法.我能想出的最好的(下面)和其他人的建议到目前为止围绕着成语/不同间接性的模式,我真的只想要一种方法来获得单一的,简单的类定义,明确指出哪些来电者(比我更细粒度),我的孩子,或绝对任何人)可以访问哪些成员.表达以下概念的最佳方式是什么?
// Can I grant Y::usesX(...) selective X::restricted(...) access more cleanly?
void Y::usesX(int n, X *x, int m) {
X::AttorneyY::restricted(*x, n);
}
struct X {
class AttorneyY; // Proxies restricted state to part or all of Y.
private:
void restricted(int); // Something preferably selectively available.
friend class AttorneyY; // Give trusted member class private access.
int personal_; // Truly private state ...
};
// Single abstract permission. Can add more friends or forwards.
class X::AttorneyY {
friend void Y::usesX(int, X *, int);
inline static void restricted(X &x, int n) { x.restricted(n); }
};
Run Code Online (Sandbox Code Playgroud)
我远不是一个软件组织大师,但感觉就像界面简单和最小特权原则在这方面的语言直接相悖.我的愿望更加明确的例子可能是一个Person与像声明的方法类takePill(Medicine *) tellTheTruth()和forfeitDollars(unsigned int)只Physician,Judge或TaxMan实例/成员方法,分别应该哪怕是考虑调用.每个主要界面方面需要一次性代理或接口类对我不好,但如果你知道我错过了什么,请说出来.
接受Drew Hall的回答:Dobbs博士 - 友谊和律师 - 客户成语
上面的代码最初称为包装类'Proxy'而不是'Attorney',并使用指针代替引用,但在其他方面等同于Drew发现的,我认为这是最常见的解决方案.(不要太苛刻地背拍自己......)我还改变了'restricted'的签名来演示参数转发.这个习语的总成本是每个权限集一个类和一个朋友声明,每个集合批准的一个朋友声明,每个权限集每个暴露方法一个转发包装器.以下大多数更好的讨论围绕着转发呼叫样板,一个非常相似的"密钥"习语避免以较少的直接保护为代价.
Mat*_* M. 62
有一个非常简单的模式,它被称为PassKey,在C++ 11中非常容易:
template <typename T>
class Key { friend T; Key() {} Key(Key const&) {} };
Run Code Online (Sandbox Code Playgroud)
随之而来的是:
class Foo;
class Bar { public: void special(int a, Key<Foo>); };
Run Code Online (Sandbox Code Playgroud)
在任何Foo方法中,呼叫站点看起来像:
Bar().special(1, {});
Run Code Online (Sandbox Code Playgroud)
注意:如果您遇到C++ 03,请跳到帖子的末尾.
代码看似简单,它嵌入了一些值得详细说明的关键点.
模式的关键是:
Bar::special需要Key<Foo>在调用者的上下文中复制aFoo构建或复制一个Key<Foo>值得注意的是:
Foo不能构造或复制,Key<Foo>因为友谊不是传递的Foo它本身不能传递Key<Foo>给任何人打电话,Bar::special因为调用它不仅需要持有实例,而且需要复制因为C++是C++,所以要避免一些问题:
public默认情况下是public by default= default would allow aggregate initialization to bypass the manual user-defined default constructor (and thus allow any type to get an instance)This is subtle enough that, for once, I advise you to copy/paste the above definition of Key verbatim rather than attempting to reproduce it from memory.
A variation allowing delegation:
class Bar { public: void special(int a, Key<Foo> const&); };
Run Code Online (Sandbox Code Playgroud)
In this variant, anyone having an instance of Key<Foo> can call Bar::special, so even though only Foo can create a Key<Foo>, it can then disseminate the credentials to trusted lieutenants.
In this variant, to avoid a rogue lieutenant leaking the key, it is possible to delete the copy constructor entirely, which allows tying the key lifetime to a particular lexical scope.
And in C++03?
Well, the idea is similar, except that friend T; is not a thing, so one has to create a new key type for each holder:
class KeyFoo { friend class Foo; KeyFoo () {} KeyFoo (KeyFoo const&) {} };
class Bar { public: void special(int a, KeyFoo); };
Run Code Online (Sandbox Code Playgroud)
The pattern is repetitive enough that it might be worth a macro to avoid typos.
Aggregate initialization is not an issue, but then again the = default syntax is not available either.
Special thanks to people who helped improving this answer over the years:
class KeyFoo: boost::noncopyable { friend class Foo; KeyFoo() {} }; completely disables the copy constructor and thus only works in the delegation variant (preventing storing instance).friend T;