C++ 提供了一个在其他语言中不常见、且常被误解的访问限定符:friend(友元)。一个类可以将另一个类或函数声明为自己的友元,从而赋予该友元对这个类所有成员的完全访问权限,包括那些声明为 protected 或 private 的成员。
有些人认为 friend 会破坏封装性。确实,如果使用不当,它可能会带来这样的问题。但其设计初衷是为了向特定的实体授予特殊的访问权限,而不是将这些成员暴露为 public 或 protected,那样反而会造成更严重的封装破坏。
举个例子,考虑以下类:thing 是一种需要由 thing_factory 根据名为 name 的文件内容构建的对象,而 thing_factory 在构造 thing 之前能够验证文件内容的合法性:
class thing {
thing(string_view); // 注意:私有成员
// ... 各种有趣的成员
// thing_factory 可以访问 thing 类的私有成员
// class thing
friend class thing_factory;
};
// 在读取到格式错误的文件时抛出此异常
class invalid_format{};
class thing_factory {
// ... 这里也有一些各种有趣的实现
string read_file(const string &name) const {
ifstream in{ name };
// 一次性读取整个文件内容,并将其作为单个字符串返回
return { istreambuf_iterator<char>{ in },
istreambuf_iterator<char>{ } };
}
bool is_valid_content(string_view) const;
public:
thing create_thing_from(const string &name) const {
auto contents = read_file(name);
if(!is_valid_content(contents))
throw invalid_format{};
// 注意:此处调用了 thing 的私有构造函数
return { contents };
}
}
我们并不希望所有人都能调用,那个接受任意 string_view 参数的私有构造函数 thing,因为该构造函数的初衷并不是处理那些未经验证的字符字符串。正因如此,我们只允许 thing_factory 使用它,从而增强了封装性。
发布代码时,通常会将一个类及其友元放在一起,它们是紧密相关的:本质上,一个类的友元是该类接口的外部扩展。最后,友元关系有一些限制。友元关系不是相互的;如果类 A 将类 B 声明为自己的友元,并不代表类 B 也会将类 A 视为友元。例如:
class A {
int n = 3;
friend class B;
public:
void f(B);
};
class B {
int m = 4;
public:
void f(A);
};
void A::f(B b) {
// int val = b.m; // 错误:A 不是 B 的友元,无法访问 B 的私有成员 m
}
void B::f(A a) {
int val = a.n; // 正确:B 是 A 的友元类,可以访问 A 的私有成员 n
}
友元关系不具有传递性;如果类 A 将类 B 声明为友元,而类 B 又将类 C 声明为友元,这并不代表类 A 也将类 C 视为友元:
class A {
int n = 3;
friend class B;
};
class B {
friend class C;
public:
void f(A a) {
int val = a.n; // 正确:B 是 A 的友元,可以访问 A 的私有成员 n
}
};
class C {
public:
void f(A a) {
// int val = a.n; // 错误:C 不是 A 的友元,无法访问 A 的私有成员 n
}
}
最后但同样重要的是,友元关系不会继承;如果类 A 将类 B 声明为友元,并不代表当 C 是 B 的派生类时,A 也将 C 视为友元:
class A {
int n = 3;
friend class B;
};
class B {
public:
void f(A a) {
int val = a.n; // 正确:B 是 A 的友元,可以访问 A 的私有成员 n
}
};
class C : B {
public:
void f(A a) {
// int val = a.n; // 错误:C 不是 A 的友元,无法访问 A 的私有成员 n
}
}
如果使用得当,friend 能够解决一些在其他情况下难以处理的封装问题,是一种强大而灵活的机制。