问题描述
为什么C ++有任何人都可以致电和friend声明的成员,并将其展示为 all private成员给定给定的外国类或方法语法将特定成员暴露于给定的呼叫者?
我想用一些例程表达界面,只能由已知的呼叫者调用,而不必让这些呼叫者完全访问所有私人,这感觉像是一件合理的事情.我能想到的最好的东西(下图)和到目前为止其他人的建议围绕成语/不同间接性的模式,我真的只想要一种方法来拥有 single ,简单的类别定义,这些定义明确指出了什么是呼叫者(比 me ,我的孩子或绝对任何人)可以访问哪些成员.表达下面概念的最佳方法是什么?
// 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); } };
我远没有成为软件组织的大师,但是感觉就像界面的简单性,而最少特权的原则在这种语言的这一方面直接矛盾.我愿望的一个更清晰的例子可能是Person类,其中包括takePill(Medicine *) tellTheTruth()和forfeitDollars(unsigned int),只有Physician,Physician,Judge或TaxMan TaxMan实例/成员方法,考虑调用.每个主要界面方面都需要一次性代理或接口类,但如果您知道我缺少某些东西,请大声疾呼.
从 Drew Hall : dobbs博士 - 友谊和律师 - 客户成语
上面的代码最初称为包装类班级"代理",而不是"律师",并使用指针代替了参考文献,但其他等同于Drew发现的内容,然后我认为这是最知名的解决方案. (不要太努力地拍打自己的背部...)我还更改了"限制"的签名,以演示参数转发.这个习语的总成本是一个类,每个权限集合一个朋友声明,每组批准的呼叫者声明一个朋友声明,每个曝光方法每次许可设置一个转发包装器.下面的大多数更好的讨论都围绕着转发呼叫样板,即一个非常相似的"键"成语避免以较少的直接保护为代价.
推荐答案
律师 - 委托人 - 委托人成语可能是您想要的.机械师与您的成员代理类解决方案没有太大不同,但是这种方式更惯用.
其他推荐答案
有一个非常简单的模式,它已被称为 passkey ,它是在C ++ 11 中非常容易p>
template <typename T> class Key { friend T; Key() {} Key(Key const&) {} };
与此:
class Foo; class Bar { public: void special(int a, Key<Foo>); };
和任何Foo方法中的呼叫站点看起来像:
Bar().special(1, {});
注意:如果您被卡在C ++ 03中,请跳至帖子的末尾.
代码看似简单,它嵌入了值得详细说明的一些关键点.
模式的症结在于:
- 调用Bar::special需要在呼叫者的上下文中复制Key<Foo>
- 只有Foo可以构建或复制Key<Foo>
值得注意的是:
- 从Foo派生的类无法构造或复制Key<Foo>因为友谊不是传递的
- Foo本身不能将Key<Foo>供任何人打电话Bar::special,因为打电话给它不仅需要持有实例,还需要制作副本
因为C ++是C ++,因此有一些避免的垃圾:
- 必须用户定义复制构造函数,否则为public默认情况下
- 默认构造函数必须是用户定义的,否则为public默认情况下
- 默认构造函数必须是 >
这足够微妙,我建议您复制/粘贴上述Key逐字定义,而不是尝试从内存中复制它.
允许委托的变化:
class Bar { public: void special(int a, Key<Foo> const&); };
在此变体中,任何具有Key<Foo>实例>的人都可以调用Bar::special,因此,即使只有Foo可以创建Key<Foo>,它也可以将凭据传播到受信任的中尉.
.在此变体中,为了避免泄漏钥匙的流氓中尉,可以完全删除复制构造函数,从而将钥匙寿命绑定到特定的词汇范围.
和C ++ 03?
好吧,这个想法是相似的,除了friend T;不是事物,因此必须为每个持有人创建一个新的密钥类型:
class KeyFoo { friend class Foo; KeyFoo () {} KeyFoo (KeyFoo const&) {} }; class Bar { public: void special(int a, KeyFoo); };
该模式的重复性足够,以至于避免错别字可能值得宏观.
汇总初始化不是问题,而是= default语法也不可用.
特别感谢多年来帮助改善此答案的人:
- luc touraille ,因为class KeyFoo: boost::noncopyable { friend class Foo; KeyFoo() {} };完全禁用复制构造函数的评论指向我,因此仅在代表团变体中起作用(防止存储实例).
- k-ballo C ++ 11改善了friend T; 的情况
其他推荐答案
您可以使用Jeff Aldger的 book 'C ++用于真正的程序员'.它没有特殊名称,但被称为"宝石和刻面".基本想法如下:在包含所有逻辑的主类中,您定义了实现该逻辑子部分的几个接口(不是真实的接口,就像它们一样).这些界面中的每个界面(就书籍而言)提供了对主要类(宝石)的某些逻辑的访问.另外,每个方面都将指针固定为宝石实例.
这对您意味着什么?
- 您可以在任何地方而不是宝石.
- facets的用户不必知道宝石结构,因为可以通过pimpl-pattern向前宣布和使用.
- 其他类可以指facet而不是宝石 - 这是您有关如何将有限方法暴露给指定类的问题的答案.
希望这会有所帮助.如果您愿意,我可以在此处发布代码样本,以更清楚地说明此模式.
编辑:这是代码:
class Foo1; // This is all the client knows about Foo1 class PFoo1 { private: Foo1* foo; public: PFoo1(); PFoo1(const PFoo1& pf); ~PFoo(); PFoo1& operator=(const PFoo1& pf); void DoSomething(); void DoSomethingElse(); }; class Foo1 { friend class PFoo1; protected: Foo1(); public: void DoSomething(); void DoSomethingElse(); }; PFoo1::PFoo1() : foo(new Foo1) {} PFoo1::PFoo(const PFoo1& pf) : foo(new Foo1(*(pf {} PFoo1::~PFoo() { delete foo; } PFoo1& PFoo1::operator=(const PFoo1& pf) { if (this != &pf) { delete foo; foo = new Foo1(*(pf.foo)); } return *this; } void PFoo1::DoSomething() { foo->DoSomething(); } void PFoo1::DoSomethingElse() { foo->DoSomethingElse(); } Foo1::Foo1() { } void Foo1::DoSomething() { cout << "Foo::DoSomething()" << endl; } void Foo1::DoSomethingElse() { cout << "Foo::DoSomethingElse()" << endl; }
edit2: 例如,您的类Foo1可能更复杂,例如,它包含另外两个方法:
void Foo1::DoAnotherThing() { cout << "Foo::DoAnotherThing()" << endl; } void Foo1::AndYetAnother() { cout << "Foo::AndYetAnother()" << endl; }
并且可以通过class PFoo2
访问它们class PFoo2 { private: Foo1* foo; public: PFoo2(); PFoo2(const PFoo1& pf); ~PFoo(); PFoo2& operator=(const PFoo2& pf); void DoAnotherThing(); void AndYetAnother(); }; void PFoo1::DoAnotherThing() { foo->DoAnotherThing(); } void PFoo1::AndYetAnother() { foo->AndYetAnother(); }
这些方法不在PFoo1类中,因此您无法通过它访问它们.通过这种方式,您可以将Foo1的行为分为两个(或更多)方面的PFOO1和PFOO2.这些方面的课程可以在不同的地方使用,他们的呼叫者不知道FOO1实现.也许这不是您真正想要的,但是对于C ++来说,您想要的是不可能的,这是一个工作圈,但也许太冗长了...
问题描述
Why does C++ have public members that anyone can call and friend declarations that expose all private members to given foreign classes or methods but offer no syntax to expose particular members to given callers?
I want to express interfaces with some routines to be invoked only by known callers without having to give those callers complete access to all privates, which feels like a reasonable thing to want. The best I could come up with myself (below) and suggestions by others so far revolve around idioms/pattern of varying indirectness, where I really just want a way to have single, simple class definitions that explicitly indicate what callers (more granularly than me, my children, or absolutely anybody) can access which members. What is the best way to express the concept below?
// 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); } };
I'm nowhere near being a software organization guru, but it feels like interface simplicity and the principle of least privilege are directly at odds in this aspect of the language. A clearer example for my desire might be a Person class with declared methods like takePill(Medicine *) tellTheTruth() and forfeitDollars(unsigned int) that only Physician, Judge, or TaxMan instances/member methods, respectively, should even consider invoking. Needing one-time proxy or interface classes for each major interface aspect sits ill with me, but please speak up if you know I'm missing something.
Answer accepted from Drew Hall: Dr Dobbs - Friendship and the Attorney-Client Idiom
The code above originally called the wrapper class 'Proxy' instead of 'Attorney' and used pointers instead of references but was otherwise equivalent to what Drew found, which I then deemed the best generally known solution. (Not to pat myself on the back too hard...) I also changed the signature of 'restricted' to demonstrate parameter forwarding. The overall cost of this idiom is one class and one friend declaration per permission set, one friend declaration per set approved caller, and one forwarding wrapper per exposed method per permission set. Most of the better discussion below revolves around the forwarding call boilerplate that a very similar 'Key' idiom avoids at the expense of less direct protection.
推荐答案
The Attorney-Client idiom may be what you're looking for. The mechanics are not too different from your member proxy class solution, but this way is more idiomatic.
其他推荐答案
There is a very simple pattern, which has retro-actively been dubbed PassKey, and which is very easy in C++11:
template <typename T> class Key { friend T; Key() {} Key(Key const&) {} };
And with that:
class Foo; class Bar { public: void special(int a, Key<Foo>); };
And the call site, in any Foo method, looks like:
Bar().special(1, {});
Note: if you are stuck in C++03, skip to the end of the post.
The code is deceptively simple, it embeds a few key points that are worth elaborating.
The crux of the pattern is that:
- calling Bar::special requires copying a Key<Foo> in the context of the caller
- only Foo can construct or copy a Key<Foo>
It is notable that:
- classes derived from Foo cannot construct or copy Key<Foo> because friendship is not transitive
- Foo itself cannot hand down a Key<Foo> for anyone to call Bar::special because calling it requires not just holding on to an instance, but making a copy
Because C++ is C++, there are a few gotchas to avoid:
- the copy constructor has to be user-defined, otherwise it is public by default
- the default constructor has to be user-defined, otherwise it is public by default
- the default constructor has to be manually defined, because = 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&); };
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); };
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:
- Luc Touraille, for pointing to me in the comments that class KeyFoo: boost::noncopyable { friend class Foo; KeyFoo() {} }; completely disables the copy constructor and thus only works in the delegation variant (preventing storing instance).
- K-ballo, for pointing out how C++11 improved the situation with friend T;
其他推荐答案
You can use a pattern described in Jeff Aldger's book 'C++ for real programmers'. It has no special name but there it is referred as 'gemstones and facets'. The basic idea is as following: among your main class that contains all the logic, you define several interfaces (not real interfaces, just like them) that implements sub-parts of that logic. Each of those interface (facet in terms of book) provides access to some of logic of main class (gemstone). Also, each facet holds the pointer to gemstone instance.
What does this mean for you?
- You can use any facet everywhere instead of gemstone.
- Users of facets doesn't have to know about gemstone structure, as in could be forward-declared and used through PIMPL-pattern.
- Other classes can refer to facet rather to gemstone - this is the answer to your question about how to expose limited nubmer of methods to specified class.
Hope this helps. If you want, I could post code samples here to illustrate this pattern more clearly.
EDIT: Here's the code:
class Foo1; // This is all the client knows about Foo1 class PFoo1 { private: Foo1* foo; public: PFoo1(); PFoo1(const PFoo1& pf); ~PFoo(); PFoo1& operator=(const PFoo1& pf); void DoSomething(); void DoSomethingElse(); }; class Foo1 { friend class PFoo1; protected: Foo1(); public: void DoSomething(); void DoSomethingElse(); }; PFoo1::PFoo1() : foo(new Foo1) {} PFoo1::PFoo(const PFoo1& pf) : foo(new Foo1(*(pf {} PFoo1::~PFoo() { delete foo; } PFoo1& PFoo1::operator=(const PFoo1& pf) { if (this != &pf) { delete foo; foo = new Foo1(*(pf.foo)); } return *this; } void PFoo1::DoSomething() { foo->DoSomething(); } void PFoo1::DoSomethingElse() { foo->DoSomethingElse(); } Foo1::Foo1() { } void Foo1::DoSomething() { cout << “Foo::DoSomething()” << endl; } void Foo1::DoSomethingElse() { cout << “Foo::DoSomethingElse()” << endl; }
EDIT2: Your class Foo1 could be more complex, for example, it contains two another methods:
void Foo1::DoAnotherThing() { cout << “Foo::DoAnotherThing()” << endl; } void Foo1::AndYetAnother() { cout << “Foo::AndYetAnother()” << endl; }
And they're accessible via class PFoo2
class PFoo2 { private: Foo1* foo; public: PFoo2(); PFoo2(const PFoo1& pf); ~PFoo(); PFoo2& operator=(const PFoo2& pf); void DoAnotherThing(); void AndYetAnother(); }; void PFoo1::DoAnotherThing() { foo->DoAnotherThing(); } void PFoo1::AndYetAnother() { foo->AndYetAnother(); }
Those methods are not in PFoo1 class, so you cannot access them through it. In this way you can split the behavior of Foo1 to two (or more) facets PFoo1 and PFoo2. Those facets classes could be used in different places, and their caller shoudn't be aware of Foo1 implementation. Maybe it's not what you really want, but what you want is impossible for C++, and this is a work-aroud, but maybe too verbose...