C++名字查找有两个方法:
一个是OL(ordinary name lookup) 普通查找规则
一个是ADL(argument-depentment lookup)依赖于实参的名字查找
一、简单引入
这两个查找规则就是C++的查找规则,如果经过这两个规则还是没有找过的话,那编译器就会报出没有匹配函数这样的错误。OL这个规则是从相邻的作用域开始进行查找,如果没有找过的话,那就到更加大的一个作用域去进行查找,OL有这样的一个规则(OL terminates as soon as the name is found),也就是说当编译器在这个作用域中找到了与要找的函数名相同的时候,这就不再会到更加大的作用域中去寻找。也就说停止在这个地方了。如果存在有重载的问题,那编译器会在找到的这个作用域内的进行考虑,到底哪个函数才是最为匹配的,但是它不会往外层进行查找了。这就有可能会形成错误。ADL规则的意思就是和字面意思差不多,编译器根据实参的类型,去包含着这些类型的名字空间中去查找我们所要的函数定义或者名字。如果是类的话,那就可能包含了它的本身还是他所有基类的名字空间,如果是模板类的话,那就是定义原型模板的名字空间和所有模板实参的名字空间。
#include <iostream> #include <string> #include <vector> #include <iterator> #include <algorithm> namespace test { class A { public: A():str_("") { } A(std::string str):str_(str) { } void setstr(std::string const &str) { this->str_ = str; } std::string getstr() const { return this->str_; } private: std::string str_; }; } std::istream& operator>>(std::istream& in,test::A& thiz) { std::string str_tmp; if(in >> str_tmp) { thiz.setstr(str_tmp); } else { thiz.setstr("wrong"); } return in; } std::ostream& operator<<(std::ostream& out,test::A const& thiz) { out<<thiz.getstr()<<std::endl; return out; } int main() { using namespace test; using namespace std; vector<A> token; copy(istream_iterator<A>(cin),istream_iterator<A>(),back_inserter(token)); copy(token.begin(),token.end(),ostream_iterator<A>(cout," ")); return 0; }这个程序的class A这个类定义在namespace test中,而<< >>这两个操作符则定义在全局作用域中,运行结果发生编译提出了
分析上述程序的名字查找的过程,copy1(copy第一次调用)会调用>>这个操作符,编译器会把当作istream的成员函数,然后去istream的类中去寻找这个函数的名字,但由于istream只是对内定的数据类型是重载的,所有不是最佳的匹配,但是编译器还是在类的作用域中找到了这个名字,所以他不会去全局中去找。
然而编译器使用ADL规则也只是找到了名字空间std和test两个,但是我们自定义的这个流操作符却是定义在全局作用域中,所以最后会报错。
如果将自己定义的<< >>包含在test中的话,那就可以通过编译运行的。
所以将相关操作符的声明和主要类型放在同一个名字空间中非常重要的。不然编译器找不到他们的....
二、ADL详解
ADL,参数相关查找,也称作为Koenig查找(以Andrew Koenig的名字命名,有兴趣可以看Scott Meyer的文章The Most Important C++ People...Ever),是指在编译器对无限定域的函数调用进行名字查找时,所应用的一种查找规则。
f(x, y, z); // unqualified
N::f(x, y, z); // qualified
上面的函数调用,第一个f就是无限定域的函数调用,第二个则限定了在名字空间N里面,也是说使用了完全限定名。
我们首先来看一个函数所在的域的分类:
1:类域(函数作为某个类的成员函数(静态或非静态))
2:名字空间域
3:全局域
而Koenig查找,它的规则就是当编译器对无限定域的函数调用进行名字查找时,除了当前名字空间域以外,也会把函数参数类型所处的名字空间加入查找的范围。
Herb提供的解释(Exceptional C++, Item 31)
Koenig Lookup(simplified): If you supply a function argument of class type (here x, of type A::X), then to look up the correct function name the compiler considers matching names in the namespace (here A) containing the argument's type.
请看下面的例程:
#include <iostream> using namespace std; namespace Koenig { class KoenigArg { public: ostream& print(ostream& out) const { out<<member_<<endl; } KoenigArg(int member = 5) : member_(member){} private: int member_; }; inline ostream& operator<<(ostream& out, const KoenigArg& kArg) { return kArg.print(out); } } int main() { Koenig::KoenigArg karg(10); cout<<karg; char c;cin>>c; return 0; }
template<typename T> void print(const T& value) { std::cout<<value; } print(karg);很显然,你的模版代码根本无法确认T是来自那个名字空间,直到编译器对模版实例化(print(karg); 被调用)。
关于Koenig查找,我们该说的都说了吗?其实未然,之前所描述的只是Koenig查找一般可能发生的状况,当Koenig查找规则和C++原来的Ordinal Lookup(OL,顺序查找规则)混合在一起的时候,它们之间的组合所产生的状况要比之前的例子复杂的多……
三、ADL和OL比较
C++名字查找有两个方法:
一个是OL(ordinary name lookup) 普通查找规则
一个是ADL(argument-depentment lookup)依赖于实参的名字查找
在说明顺序查找和Koenig查找如何共同作用的之前,先解释一下顺序查找,所谓顺序查找,就是从函数调用所处的域开始(如果函数调用处于一个成员函数中,初始域就是类域,如果处于自由函数中,初始域就是名字空间域或者全局域),依次由内到外到各个域进行名字查找,如果在某个域找到该名字的函数,就停止查找,将所有找到的重载函数进行重载决议,如果没有合适的候选者或者有多个合适的候选者而导致歧义,编译器则报错。如果一直找到全局域也没有找到任何该名字函数,编译器也报错。
例如:
namespace KL { namespace KL_Inside { class KoenigLookup { public: void koenigLookup() { KoenigLookupMethod(); } }; } }
在继续阐述这一点之前,首先确认一个原则,类域比名字空间域(包括全局域)有更高的优先级,KL规则的作用范围是名字空间域里的自由函数,当OL应用于类域的成员函数的时候,KL是不起作用的。或者按照Herb的话说,成员函数与类之间的关系要比非成员函数更紧密(虽然都可以认为是类接口的一部分),当进行名字查找的时候,成员函数绝对不会跟非成员函数一起进行重载决议。
#include <iostream> using namespace std; namespace KL_ARG_2 { class KoenigLookupArg2; } namespace KL_ARG_1 { class KoenigLookupArg { }; //Overload method in namespace KL_ARG_1, same with KoenigLookupArg void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL_ARG_1::KoenigLookupMethod "; } } namespace KL_ARG_2 { class KoenigLookupArg2 { }; //Overload method in namespace KL_ARG_2, same with KoenigLookupArg2 void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL_ARG_2::KoenigLookupMethod "; } } //Overload method is Global void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Global KoenigLookupMethod "; } namespace KL { //Overload method in namespace KL void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL::KoenigLookupMethod "; } namespace KL_Inside { //Overload method in namespace KL::KL_Inside void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL::KL_Inside::KoenigLookupMethod "; } //Call overload method in the scope of namespace KL::KL_Inside void KL_KoenigLookup() { KL_ARG_1::KoenigLookupArg klArg; KL_ARG_2::KoenigLookupArg2 klArg2; KoenigLookupMethod(klArg, klArg2); } class KoenigLookup { public: //Call overload method in the scope of class KoenigLookup //Non-Static member function void koenigLookup() { KL_ARG_1::KoenigLookupArg klArg; KL_ARG_2::KoenigLookupArg2 klArg2; KoenigLookupMethod(klArg, klArg2); } //Call lookup method in the scope of class KoenigLookup //Static Member function static void staticKoenigLookup() { KL_ARG_1::KoenigLookupArg klArg; KL_ARG_2::KoenigLookupArg2 klArg2; KoenigLookupMethod(klArg, klArg2); } private: //Overload method in class KoenigLookup(Non-Static member function) void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Non-Static Member KL::KL_Inside::KoenigLookup::" "KoenigLookupMethod "; } //Overload method in class KoenigLookup(Static member function) static void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Static Member KL::KL_Inside::KoenigLookup::" "KoenigLookupMethod "; } }; } } int main() { //1, Call overload method in the scope of class KoenigLookup(namespace KL) // Non-Static member function KL::KL_Inside::KoenigLookup kl; kl.koenigLookup(); //2, Call overload method in the scope of class KoenigLookup(namespace KL) // Static member function KL::KL_Inside::KoenigLookup::staticKoenigLookup(); //3, Call overload method in the scope of namespace KL KL::KL_Inside::KL_KoenigLookup(); //4, Call overload method in the scope of global KL_ARG_1::KoenigLookupArg klArg; KL_ARG_2::KoenigLookupArg2 klArg2; KoenigLookupMethod(klArg, klArg2); char c;cin>>c; return 0; }
当然,上面的程序是不会被编译通过的,它是各种可能的组合镜像,我们用它删节之后的子版本来说明各种状况。
A:首先,我们来看重载函数KoenigLookupMethod的调用发生在类KoenigLookup的成员函数koenigLookup(非静态)的情况。
#include <iostream> using namespace std; namespace KL_ARG_2 { class KoenigLookupArg2; } namespace KL_ARG_1 { class KoenigLookupArg { }; //Overload method in namespace KL_ARG_1, same with KoenigLookupArg void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL_ARG_1::KoenigLookupMethod "; } } namespace KL_ARG_2 { class KoenigLookupArg2 { }; //Overload method in namespace KL_ARG_2, same with KoenigLookupArg2 void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL_ARG_2::KoenigLookupMethod "; } } //Overload method is Global void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Global KoenigLookupMethod "; } namespace KL { //Overload method in namespace KL void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL::KoenigLookupMethod "; } namespace KL_Inside { //Overload method in namespace KL::KL_Inside void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL::KL_Inside::KoenigLookupMethod "; } class KoenigLookup { public: //Call overload method in the scope of class KoenigLookup //Non-Static member function void koenigLookup() { KL_ARG_1::KoenigLookupArg klArg; KL_ARG_2::KoenigLookupArg2 klArg2; KoenigLookupMethod(klArg, klArg2); } private: //Overload method in class KoenigLookup(Non-Static member function) void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Non-Static Member KL::KL_Inside::KoenigLookup::" "KoenigLookupMethod "; } }; } } int main() { //1, Call overload method in the scope of class KoenigLookup(namespace KL) // Non-Static member function KL::KL_Inside::KoenigLookup kl; kl.koenigLookup(); char c;cin>>c; return 0; }上述程序是可以编译通过并运行的,输出结果是“Non-Static Member KL::KL_Inside::KoenigLookup::KoenigLookupMethod”,被调用的是类KoenigLookup的成员函数KoenigLookupMethod(非静态)。整个过程中KL规则并没有起作用,因为OL开始作用于类域,找到符合名字的成员函数之后就停止了查找,经过重载决议后得到最后调用的版本,类域中KL是不起作用的,我们把上面程序的调用函数和重载函数换作类的静态函数,结果也一样:
class KoenigLookup { public: //Call lookup method in the scope of class KoenigLookup //Static Member function static void staticKoenigLookup() { KL_ARG_1::KoenigLookupArg klArg; KL_ARG_2::KoenigLookupArg2 klArg2; KoenigLookupMethod(klArg, klArg2); } //Overload method in class KoenigLookup(Static member function) static void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Static Member KL::KL_Inside::KoenigLookup::" "KoenigLookupMethod "; } }; int main() { //2, Call overload method in the scope of class KoenigLookup(namespace KL) // Static member function KL::KL_Inside::KoenigLookup::staticKoenigLookup(); char c;cin>>c; return 0; }
static void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&, int)编译器会直接报错说签名不吻合,并不会到后续的域继续进行查找,这也是所谓的name hiding名字隐藏。
B:继续考查KoenigLookupMethod调用发生在类KoenigLookup的成员函数中,但是在类KoenigLookup中没有找到任何候选者的情况,此时,根据OL,查找的域步进到了内层名字空间KL::KL_Inside中,如果在这个域找到了候选者,那么编译器此时就会附加KL规则,试图从KoenigLookupMethod的参数相关的域KL_ARG_1和KL_ARG_2中查找更多候选者参加重载决议,例如:
#include <iostream> using namespace std; namespace KL_ARG_2 { class KoenigLookupArg2; } namespace KL_ARG_1 { class KoenigLookupArg { }; //Overload method in namespace KL_ARG_1, same with KoenigLookupArg void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL_ARG_1::KoenigLookupMethod "; } } namespace KL_ARG_2 { class KoenigLookupArg2 { }; //Overload method in namespace KL_ARG_2, same with KoenigLookupArg2 void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL_ARG_2::KoenigLookupMethod "; } } //Overload method is Global void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Global KoenigLookupMethod "; } namespace KL { //Overload method in namespace KL void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL::KoenigLookupMethod "; } namespace KL_Inside { //Overload method in namespace KL::KL_Inside void KoenigLookupMethod(KL_ARG_1::KoenigLookupArg&, KL_ARG_2::KoenigLookupArg2&) { cout<<"Namespace KL::KL_Inside::KoenigLookupMethod "; } class KoenigLookup { public: //Call lookup method in the scope of class KoenigLookup //Static Member function static void staticKoenigLookup() { KL_ARG_1::KoenigLookupArg klArg; KL_ARG_2::KoenigLookupArg2 klArg2; KoenigLookupMethod(klArg, klArg2); } }; } } int main() { //2, Call overload method in the scope of class KoenigLookup(namespace KL) // Static member function KL::KL_Inside::KoenigLookup::staticKoenigLookup(); char c;cin>>c; return 0; }上面的程序会使编译器报错说对重载函数的调用不明确(VC 8.0),有三个可能性:
试图匹配参数列表“(KL_ARG_1::KoenigLookupArg, KL_ARG_2::KoenigLookupArg2)”时正在编译...
main.cpp
d:/devtest/learning/koeniglookup2/main.cpp(71) : error C2668: “KL::KL_Inside::KoenigLookupMethod”: 对重载函数的调用不明确
d:/devtest/learning/koeniglookup2/main.cpp(56): 可能是“void KL::KL_Inside::KoenigLookupMethod(KL_ARG_1::KoenigLookupArg &,KL_ARG_2::KoenigLookupArg2 &)”
d:/devtest/learning/koeniglookup2/main.cpp(30): 或“void KL_ARG_2::KoenigLookupMethod(KL_ARG_1::KoenigLookupArg &,KL_ARG_2::KoenigLookupArg2 &)”[使用参数相关的查找找到]
d:/devtest/learning/koeniglookup2/main.cpp(16): 或“void KL_ARG_1::KoenigLookupMethod(KL_ARG_1::KoenigLookupArg &,KL_ARG_2::KoenigLookupArg2 &)”[使用参数相关的查找找到]
C:假设在名字空间KL_Inside里面找不到,那么查找的域继续步进到外层名字空间域KL,如果在该名字空间找到候选者,编译器一样附加KL规则从参数相关域KL_ARG_1和KL_ARG_2中查找更多候选者参与重载决议。
D:如果KL还是找不到,那么查找的域最后来到了全局域,因为OL不会再继续步进,所以编译器直接使用KL规则,在全局域和参数相关域KL_ARG_1和KL_ARG_2中进行联合查找,将找到的候选者参与重载决议。
整个名字查找过程至此就结束了,如果还没有找到的话,编译器就会告诉你没有KoenigLookupMethod这个标识符。示例代码在VC 8.0 和 Gnu C++ 3.4.2中编译验证通过。
参考资料:http://blog.csdn.net/ccjjnn19890720/article/details/6538775
http://blog.csdn.net/rogeryi/article/details/1448606 \ http://blog.csdn.net/rogeryi/article/details/1449220