起因是我写了这么一段代码:
template <typename type> class LIFO_stack : public my_stack<type> { template <typename ano_type> friend ostream& operator<<(ostream& os, LIFO_stack<ano_type>& s); } template <typename type> ostream& operator<<(ostream& os, LIFO_stack<type>& s) { auto it = --s.vec.end(); for (; it != s.vec.begin(); --it) { os << *it << ' '; } os << *it << endl; return os; }
然后跑去问jp同学是不是这种友元函数一定要在template类里面再声明一个template才行,别的方法可不可以。
然后jp同学扔了这么一段代码给我,说:可以。
我当时就蒙了:????等等这都写在类里面了!!「<<」操作符不是不能作为成员函数吗!
jp同学说:你看仔细点,这是友元,不算成员函数。
还有这种操作??这是什么友元黑魔法?
然后我又试着自己写了一段代码尝试一下:
报错了【。
这不科学啊!明明这个「+=」操作符也是友元函数啊!凭什么这个就不能通过编译!
然后再经过jp同学的提示,把这个函数放在所操作的类里面作为友元并定义,结果居然!通!过!了!
凭什么哦,同样是友元为什么待遇不一样
后来经过jp同学和我(其实主要还是jp同学 唔唔唔jp大佬太强了 扑通)的查找,终于在这么个答案里面找到了原因:
可以在类声明中定义友元函数。 这些函数是内联函数,类似于成员内联函数,其行为就像它们在所有类成员显示后但在类范围关闭前(类声明的结尾)被定义时的行为一样。
类声明中定义的友元函数不被认为在封闭类的范围内;它们在文件范围内。
hello()的确被定义为全局函数,但是外面调用hello()的时候找不到函数定义,实际上根本就不知道这个函数被定义过,因为在作用域内没有声明。在外面加一行设声明即可。
参见C++标准7.3.1.2节第三段:
The name of the friend is not found by unqualified lookup (3.4.1) or by qualified lookup (3.4.3) until a matching declaration is provided in that namespace scope (either before or after the class definition granting friendship). If a friend function is called, its name may be found by the name lookup that considers functions from namespaces and classes associated with the types of the function arguments (3.4.2).
C++标准3.4.2节(Argument-dependent name lookup)第四段:
Any namespace-scope friend functions or friend function templates declared in associated classes are visible within their respective namespaces even if they are not visible during an ordinary lookup (11.3).
也就是说,除非友元函数带了class A的参数,可以通过Argument-dependent name lookup(ADL)被找到,否则必须在作用域里有声明才能在作用域中可见。
这么复杂的吗
原因总算是找到了,在类内被定义的友元函数实际上的作用域是整个文件,所以实际上不仅是这个类,这个文件内都可以访问这个函数。
那为什么写在所操作的类内就可以调用,除此之外就不能调用呢?
其实是因为编译器找不到入口找(look up)这个函数。
如果写在所操作的类内,编译器就可以通过unqualified lookup中的Argument-dependent name lookup(ADL)找到这个函数,除此之外就就找不到,所以就无法调用。
那我们无论如何都想调用怎么办?
实际上,很简单,这属于有定义(definition)没声明(declaration)的情况,我们只需要在文件域声明一次这个函数就行了。
也就是说,我们掌握了一个友元黑魔法,以后想要定义操作自身类成员的友元函数的时候,直接定义在类内就行了,不需要在类外定义。特别是「<<」操作符采用这种方法就特别方便。
例如我一开始写的代码可以改成这样:
template <typename type> class LIFO_stack : public my_stack<type> { friend ostream& operator<<(ostream& os, const LIFO_stack& s) { auto it = --s.vec.end(); for (; it != s.vec.begin(); --it) { os << *it << ' '; } os << *it << endl; return os; } }
可以达到同样的效果。
如果要更深入的话,就要讨论到qualified name lookup和unqualified name lookup了。
详细的定义可以看这里:
https://en.cppreference.com/w/cpp/language/qualified_lookup
https://en.cppreference.com/w/cpp/language/unqualified_lookup
这一段我也还没有研究透,还是等研究透了再更新吧。
是神仙斗法呜呜呜