在处理运算符时,非成员函数有很大的优势。
大多数运算符是二元的(带两个参数)并且有些对称,并且对于成员运算符,它们仅在 *this 是左侧参数时才有效。所以你需要使用非成员运算符。
友元模式既赋予操作员对类的完全访问权限(而且,操作员通常足够亲密以至于这无害),而且它使操作员在 ADL 之外不可见。此外,如果它是template 类,则具有显着的优势。
在 ADL 之外不可见让你做这样疯狂的事情:
struct bob {
template<class Lhs, class Rhs>
friend bob operator+( Lhs&&, Rhs&& ) { /* implementation */ }
}
这里我们的operator+ 是template,看起来可以匹配任何东西。除了因为它只能在bob 上通过ADL 找到之外,只有在至少一个bob 对象上使用它才会匹配。这种技术可以让您了解右值/左值重载,对类型的属性进行 SFINAE 测试等。
template 类型的另一个优点是运算符最终不是template 函数。 look here:
template<class T>
struct X {};
template<class T>
bool operator==( X<T>, X<T> ) { return true; }
template<class T>
struct Y {
friend bool operator==( Y, Y ) { return true; }
};
struct A {
template<class T>
operator X<T>() const { return {}; }
};
struct B {
template<class T>
operator Y<T>() const { return {}; }
};
int main() {
A a;
X<int> x;
B b;
Y<int> y;
b == y; // <-- works!
a == x; // <-- fails to compile!
}
X<T> 有一个template operator==,而Y<T> 有一个friend operator==。 template 版本必须将两个参数模式匹配为X<T>,否则会失败。因此,当我传入 X<T> 和可转换为 X<T> 的类型时,它无法编译,因为模式匹配不会进行用户定义的转换。
另一方面,Y<T> 的operator== 不是template 函数。所以当b == y被调用时,找到了(通过y上的ADL),然后对b进行测试,看是否可以转换为y(可以),调用成功。
template 具有模式匹配的运算符很脆弱。您可以在标准库中的几个点看到这个问题,其中运算符以阻止转换工作的方式重载。如果将运算符声明为friend operator 而不是公共免费的template 运算符,则可以避免此问题。