Step into Chromium (100) - PassKey
PassKey 是什么?
字如其名,PassKey 是一个钥匙,在 base/types/pass_key.h 中定义,存在 base namespace 下面,允许预先声明的类访问某些特定方法,而不必使用类级别的友元声明。
先来看一下它的最简单定义:
template <typename... Args>
class PassKey;
template <typename T>
class PassKey<T> {
friend T;
PassKey() = default;
};就这么简单。神奇吗?
怎么用?
比如我们有 A, B 和 C 三个类,其中只想 B 访问 A 的 foo() 方法,那么可以这样写:
class B;
class A {
public:
// bar 方法可以被任意类调用
void bar(){}
// foo 方法只能被 B 调用
void foo(PassKey<B>){}
};
class B {
public:
void foo(){
A a;
a.foo({}); // OK!
}
};
class C {
public:
void foo(){
A a;
// a.foo({}); // Error under C++20 or higher
}
};发现了吗?只有 B 是 PassKey<B> 的友元类,也就只有 B 可以实例化 PassKey<B>,从而调用 A::foo()。
警告
这个约束只在 C++20 以上生效,因为在以前 PassKey<B> 这种没有用户定义的构造函数的类是允许聚合初始化从而绕过检查的,但是 C++ 20 认为只要用户显式写了 = default 或者 = delete,那么就不能认为源类是一个聚合体了,必须调用构造函数,从而触发访问检查。
不过你可能会发现,这里有一个问题,就是如果 B 实例化 PassKey<B> 之后传给第三方,那么第三方类也可以拿这个实例来调用 A::foo() 了。这就好像 B 拥有 A::foo() 的钥匙,但是钥匙是可以复刻的,那么任何拿到复刻钥匙的实体都可以打开 A::foo() 这把锁。针对这个问题,我们还可以禁用拷贝构造和赋值操作符,这样就得到了 NonCopyablePassKey:
template <typename T>
class NonCopyablePassKey {
friend T;
NonCopyablePassKey() = default;
NonCopyablePassKey(const NonCopyablePassKey&) = delete;
NonCopyablePassKey& operator=(const NonCopyablePassKey&) = delete;
};常用的场景就是只允许某个 Factory 来构造某类 Object ,给 Object 的构造函数加一个 Passkey<Factory> 参数即可。在 Chromium 中,也常用来管控一些设计上正在被废弃的类在新场景下的利用。
此外,PassKey 在较新版本也加入了多类的支持,如下:
有点长,而且是偏特化大师和前沿特性大神(需要 requires 和折叠表达式,至少 C++20)...
展开
namespace pass_key_internal {
template <typename T, typename... Args>
concept OneOf = (std::same_as<T, Args> || ...);
// Calculates how many instances of <T> there are among <Args...>.
template <typename T, typename... Args>
constexpr int type_frequency =
(static_cast<int>(std::is_same_v<T, Args>) + ... + 0);
// All types in <Args...> are unique if each type occurs there exactly once!
template <typename... Args>
concept PairwiseUnique = ((type_frequency<Args, Args...> == 1) && ...);
} // namespace pass_key_internal
// Specialization for multi-argument PassKey. This allows a PassKey to be
// constructed from another PassKey if all its arguments are present in
// the target PassKey's argument list. This enables flexible access control
// where a function might accept a PassKey that covers a broader set of
// authorized callers, and callers can provide more specific PassKey-s.
template <typename... Args>
requires(sizeof...(Args) > 1)
class PassKey<Args...> {
static_assert(pass_key_internal::PairwiseUnique<Args...>,
"PassKey<> arguments must be pairwise unique.");
public:
// Allows constructing a PassKey<A, B> from PassKey<A> or PassKey<B>.
// Implicit to allow feeding PassKey<A> into a function that expects
// PassKey<A, B>.
template <typename T>
requires pass_key_internal::OneOf<T, Args...>
// NOLINTNEXTLINE(google-explicit-constructor)
PassKey(PassKey<T>) {}
// Allows constructing a PassKey<A, B, ...> from a PassKey<A, B>.
// The exact order of arguments doesn't matter.
// Implicit to allow feeding PassKey<A, B> into a function that expects
// PassKey<A, B, ...>.
template <typename... SourceArgs>
requires(sizeof...(SourceArgs) > 1 &&
(pass_key_internal::OneOf<SourceArgs, Args...> && ...))
// NOLINTNEXTLINE(google-explicit-constructor)
PassKey(PassKey<SourceArgs...>) {}
};