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。正常的重载解析开销非常大。使用隐藏友元,我们可以减少这个开销。