干净的C++粒度朋友相当于?(答案:律师 - 客户成语)

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,JudgeTaxMan实例/成员方法,分别应该哪怕是考虑调用.每个主要界面方面需要一次性代理或接口类对我不好,但如果你知道我错过了什么,请说出来.

接受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>在调用者的上下文中复制a
  • 只能Foo构建或复制一个Key<Foo>

值得注意的是:

  • 派生的类Foo不能构造或复制,Key<Foo>因为友谊不是传递的
  • Foo它本身不能传递Key<Foo>给任何人打电话,Bar::special因为调用它不仅需要持有实例,而且需要复制

因为C++是C++,所以要避免一些问题:

  • 复制构造函数必须是用户定义的,否则public默认情况下是
  • 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&); };
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:

  • 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,用于指出C++ 11如何改善这种情况friend T;

  • 并且不要忘记令人敬畏的调用语法:`Bar b; b.special( 3, {} );` 我们也可以做 `static structunlock_request_t {} use_key; template&lt;class T&gt; class only_allow { 朋友类 T; only_allow(解锁) {}; only_allow(only_allow const&amp;)=删除;}`,它将访问模式更改为“Bar b;” b.special( 3, use_key );` ;) (2认同)

Dre*_*all 17

律师-客户成语可能是你在找什么.机制与您的成员代理类解决方案没有太大区别,但这种方式更具惯用性.