有一个非常简单的模式,后来被称为PassKey,这是在 C++11 中非常简单:
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 困境,请跳至本文末尾。
该代码看似简单,但它嵌入了一些值得详细阐述的关键点。
该模式的关键在于:
- calling
Bar::special
需要复制一个Key<Foo>
在调用者的上下文中
- only
Foo
可以构造或复制一个Key<Foo>
值得注意的是:
- 派生类
Foo
无法构造或复制Key<Foo>
因为友谊是不可传递的
-
Foo
本身无法传承Key<Foo>
任何人都可以打电话Bar::special
因为调用它不仅需要保留一个实例,还需要制作一个副本
因为 C++ 就是 C++,所以有一些问题需要避免:
- 复制构造函数必须是用户定义的,否则就是
public
默认情况下
- 默认构造函数必须是用户定义的,否则就是
public
默认情况下
- 默认构造函数必须是manually定义,因为
= default
将允许聚合初始化绕过手动用户定义的默认构造函数(从而允许任何类型获取实例)
这非常微妙,我建议您复制/粘贴上面的定义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
语法也不可用。
特别感谢多年来帮助改进这个答案的人们:
-
吕克·图雷耶,在评论中指出我
class KeyFoo: boost::noncopyable { friend class Foo; KeyFoo() {} };
完全禁用复制构造函数,因此仅适用于委托变体(防止存储实例)。
-
K-ballo,指出 C++11 如何改善这种情况
friend T;