Hidden friends in C++
use friend while overloading operators for struct
MSVC 对像 std::setw 这样的流操纵符的实现需要重载 operator>> 和 <<。这种重载使用了一种称为“隐藏友元惯用法 (Hidden Friend Idiom)”的编码方法来加快编译时间。实际上,隐藏友元被广泛使用以提高编译效率。
隐藏友元是一个在类定义中作为友元定义的自由函数(通常是运算符重载)。有的时候这是必要的。例如,
1class T1
2{
3private:
4 int member;
5public:
6 std::ostream & operator<<(std::ostream & os)
7 {
8 os << member;
9 return os;
10 }
11};operator<< 被重载为 std::ostream。这意味着如果我们想打印出 member,我们应该像这样写:
1T1 t;
2t << std::cout;这比较奇怪。但是如果我们使用一个友元,
1public:
2 friend std::ostream & operator<<(std::ostream & os, T1 tt)
3 {
4 os << tt.member;
5 return os;
6 }这样就可以写比较正常的 std::cout << t; 了。
然而,如果对象是一个结构体,那么就不需要友元,因为所有成员都是 public 的。
1struct T2 {int member;};
2
3std::ostream & operator<<(std::ostream & os, T2 tt)
4{
5 os << tt.member;
6 return os;
7}学 OI 的时候似乎大家都这样重载,从来没有考虑过其他的写法。
但是一个常常被忽略的关键点是,重载解析需要编译器花费大量时间来查找函数。如果我们在块作用域中声明 operator<< 作为友元,编译器需要选择的重载集就会更小。例子:
1struct Foo
2{
3 int member;
4 friend std::ostream &operator<<(std::ostream &os, const Foo &f) // fn1
5 {
6 os << f.member;
7 return os;
8 }
9};
10
11std::ostream &operator<<(std::ostream &os, int value) { /* ... */ } //fn2
12
13int main()
14{
15 int i = 42;
16 Foo f = createFoo();
17
18 std::cout << i; // #1
19 std::cout << f; // #2
20
21 return 0;
22}在 #1 处,编译器只能看到 fn1。在 #2 处,编译器将使用参数相关查找(Argument-Dependent Lookup, ADL)来找到 fn2。正常的重载解析开销非常大。使用隐藏友元,我们可以减少这个开销。