您的位置:首页 > 编程语言 > C语言/C++

c++编译器名字查找规则之ADL和Ordinal Lookup比较

2014-01-11 23:07 330 查看
c++编译器名字查找有两个规则:
1 老的Ordinal Lookup
2 ADL(argument-depentment lookup)

在说明Ordinal Lookup(顺序查找)和Koenig查找如何共同作用的之前,先解释一下Ordinal Lookup顺序查找,所谓顺序查找,就是从函数调用所处的域开始(如果函数调用处于一个成员函数中,初始域就是类域,如果处于自由函数中,初始域就是名字空间域或者全局域),依次由内到外到各个域进行名字查找,如果在某个域找到该名字的函数,就停止查找,将在该域找到的所有重载函数进行重载决议,如果没有合适的候选者或者有多个合适的候选者而导致歧义,编译器则报错。如果一直找到全局域也没有找到任何该名字函数,编译器也报错。

补充说明:
如果是通过一个类的实例调用类的成员函数,则应该是从该实例对应的类域开始,到该类的基类域(如果有基类的话),然后再是实例调用类的成员函数所处的域开始,接着如上所述。

例如:


namespace KL


{


namespace KL_Inside


{


class KoenigLookup


{


public:


void koenigLookup()


{


KoenigLookupMethod();


}


};


}


}

KoenigLookupMethod的查找顺序依次是类KoenigLookup,名字空间KL::KL_Inside,名字空间KL,最后是全局域。

应该说,OL是名字查找的主要规则,只是在OL应用的某些阶段中KL也起作用,并将其作用附加在OL之上。

在继续阐述这一点之前,首先确认一个原则,类域比名字空间域(包括全局域)有更高的优先级,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;


}

[align=left] [/align]
当然,上面的程序是不会被编译通过的,它是各种可能的组合镜像,我们用它删节之后的子版本来说明各种状况。

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 Member KL::KL_Inside::KoenigLookup:: KoenigLookupMethod”。

如果成员函数KoenigLookupMethod不合适怎么办,比如它的签名被修改如下:


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),有三个可能性:

正在编译...

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 &)”[使用参数相关的查找找到]

试图匹配参数列表“(KL_ARG_1::KoenigLookupArg, KL_ARG_2::KoenigLookupArg2)”时

值得注意的是,全局域和名字空间KL的重载函数KoenigLookupMethod并没有被编译器抱怨说是导致歧义的版本,因为这两个域此时根本不在查找的范围内。

C:

假设在名字空间KL_Inside里面找不到,那么查找的域继续步进到外层名字空间域KL,如果在该名字空间找到候选者,编译器一样附加KL规则从参数相关域KL_ARG_1和KL_ARG_2中查找更多候选者参与重载决议。

D:

如果KL还是找不到,那么查找的域最后来到了全局域,因为OL不会再继续步进,所以编译器直接使用KL规则,在全局域和参数相关域KL_ARG_1和KL_ARG_2中进行联合查找,将找到的候选者参与重载决议。

整个名字查找过程至此就结束了,如果还没有找到的话,编译器就会告诉你没有KoenigLookupMethod这个标识符。

最后看一下调用发生在非成员函数里面的情况:

如果KoenigLookupMethod调用发生在名字空间KL_Inside的自由函数中,情况与前述的B类似;

如果KoenigLookupMethod调用发生在名字空间KL的自由函数中,情况与前述的C类似;

如果KoenigLookupMethod调用发生在全局域的自由函数中,情况则与前述的D类似。

至此,函数的名字查找规则(包括OL和KL)应该都解析的比较清楚了,当然,我们在编程中一般遇到的状况不会像上面几个例子那样那么复杂,实际上也不应该去搞得这么复杂,过于复杂的事物即使不会使我们犯错,也会使未来的我们代码的维护者犯错。但是,有时我们在维护他人代码或者使用一些模版库,碰到KL规则带来的副作用的时候,我们应该懂得如何去识别和解决它。

PS:

示例代码在VC 8.0 和 Gnu C++ 3.4.2中编译验证通过。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: