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