Roger( roger2yi@gmail.com.cn)
在说明顺序查找和Koenig查找如何共同作用的之前,先解释一下顺序查找,所谓顺序查找,就是从函数调用所处的域开始(如果函数调用处于一个成员函数中,初始域就是类域,如果处于自由函数中,初始域就是名字空间域或者全局域),依次由内到外到各个域进行名字查找,如果在某个域找到该名字的函数,就停止查找,将所有找到的重载函数进行重载决议,如果没有合适的候选者或者有多个合适的候选者而导致歧义,编译器则报错。如果一直找到全局域也没有找到任何该名字函数,编译器也报错。
{
namespace KL_Inside
{
class KoenigLookup
{
public:
void koenigLookup()
{
KoenigLookupMethod();
}
};
}
}
KoenigLookupMethod的查找顺序依次是类KoenigLookup,名字空间KL::KL_Inside,名字空间KL,最后是全局域。
应该说,OL是名字查找的主要规则,只是在OL应用的某些阶段中KL也起作用,并将其作用附加在OL之上。
在继续阐述这一点之前,首先确认一个原则,类域比名字空间域(包括全局域)有更高的优先级,KL规则的作用范围是名字空间域里的自由函数,当OL应用于类域的成员函数的时候,KL是不起作用的。或者按照Herb的话说,成员函数与类之间的关系要比非成员函数更紧密(虽然都可以认为是类接口的一部分),当进行名字查找的时候,成员函数绝对不会跟非成员函数一起进行重载决议。
下面的例程说明了整个名字查找规则可能发生的各种各样的状况:
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(非静态)的情况。
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是不起作用的,我们把上面程序的调用函数和重载函数换作类的静态函数,结果也一样:
{
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不合适怎么办,比如它的签名被修改如下:
编译器会直接报错说签名不吻合,并不会到后续的域继续进行查找,这也是所谓的name hiding名字隐藏。
B:
继续考查KoenigLookupMethod调用发生在类KoenigLookup的成员函数中,但是在类KoenigLookup中没有找到任何候选者的情况,此时,根据OL,查找的域步进到了内层名字空间KL::KL_Inside中,如果在这个域找到了候选者,那么编译器此时就会附加KL规则,试图从KoenigLookupMethod的参数相关的域KL_ARG_1和KL_ARG_2中查找更多候选者参加重载决议,例如:
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 中编译验证通过。