C++ Name Lookup
在 C++ 中, 函数的调用有时很简单:
// code 1
void foo()
{}
int main()
{
foo();
return 0;
}
有时可能很复杂:
// code 2
namespace NA{
class CA {};
void foo(CA &)
{}
}
int main()
{
NA::CA ca;
foo(ca);
return 0;
}
下面我就总结一下 C++ 中的 Name Lookup.
当编译器遇到一个函数调用时:
- Koenig Name Lookup: 查找当前命名空间中是否有匹配的函数声明. 如果函数中有类类型的参数, 那么这个类参数所在的命名空间也会被考虑到函数查找的范围内. 而且这两部分命名空间并没有先后顺序. 因此 code 2 能被编译通过, 然而如下代码却无法通过:
// code 3
namespace NA{
class CA {};
void foo(CA &) // 1
{}
}
void foo(NA::CA &) // 2
{}
int main()
{
NA::CA ca;
foo(ca); // ambiguous!
return 0;
}
1 和 2 的地位是一样的, 因此 foo(ca) 是无法决议的.
- 成员函数的关系比非成员函数有更高的备选优先级.
void bar() // 1
{
std::cout <<"Global::bar()" <<std::endl;
}
class T
{
public:
void foo()
{
bar();
}
void bar() // 2
{
std::cout <<"T::bar()" <<std::endl;
}
};
int main()
{
T t;
t.foo(); // T::bar()
return 0;
}
以上来自: http://www.gotw.ca/publications/mill02.htm#1
http://www.gotw.ca/gotw/030.htm
再来看看我自己整理的思路. 首先还是来看两段程序:
namespace NX
{
void test() // 1
{
cout <<"NX::test()" <<endl;
}
namespace NY
{
void test() // 2
{
cout <<"NX::NY::test()" <<endl;
}
void fun()
{
test();
}
}
}
int main(int argc, char *argv[])
{
//NA::NB::TA t;
//bar(t);
NX::NY::fun(); // 调用了 2
return 0;
}
这个例子是很显然的, 因为 2 将 1 覆盖了. 如果在当前明明空间内没有找到函数, 则会向其外部命名空间继续寻找.
再看一个
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
namespace NA
{
namespace NB
{
class TA
{};
void bar(NB::TA &) // 1
{
cout <<"NA::NB::bar()" <<endl;
}
}
void bar(NB::TA &) // 2
{
cout <<"NA::bar()" <<endl;
}
}
int main(int argc, char *argv[])
{
NA::NB::TA t;
bar(t); // 调用了 1, 实际上通过 Koenig Lookup 根本无法找到 2. 原因见下.
return 0;
}
这是也是很容易理解的, 通过 Koenig Lookup, 在 bar 进行调用的时候, 如果其参数是类类型, 则其参数所在的命名空间也会被加入到函数查找的范围内.
但是要补充的一点就是, 参数所在的命名空间的外层命名空间不会被加入到函数查找的范围内.
看下面的证明:
// example 1
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
namespace NA
{
namespace NB
{
class TA
{};
/*
void bar(NB::TA &) // 1. 在 NA::NB 命名空间下不存在 bar 函数了
{
cout <<"NA::NB::bar()" <<endl;
}
*/
}
void bar(NB::TA &) // 2. 但是在 NA 命名空间下依然存在 bar 函数.
{
cout <<"NA::bar()" <<endl;
}
}
int main(int argc, char *argv[])
{
NA::NB::TA t;
bar(t); // 这里发生编译错误, 因为找不到函数 bar. 这就是函数参数的外部命名空间不会被加入到函数查找中.
return 0;
}
此外, 一个函数中的多个类对象所引入的不同命名空间的优先级是一样的, 因此如下代码会产生 ambiguous:
// example 2
#include <iostream>
#include <string>
using namespace std;
namespace NB // 这样向前声明不同命名空间的类.
{
class TB;
}
namespace NA
{
class TA{};
// class NB::TB; // 这样声明不同命名空间的类是错误的
void bar(TA &, NB::TB &) // 1
{
cout <<"NA::bar()" <<endl;
}
}
namespace NB
{
class TB{};
void bar(NA::TA &, TB &) // 2
{
cout <<"NB::bar()" <<endl;
}
}
int main(int argc, char *argv[])
{
NA::TA ta;
NB::TB tb;
bar(ta, tb); // 这里发生编译错误, 由于 Koenig Lookup 导致 NA:: 和 NB:: 两个命名空间都被同等地位的查找 bar 函数, 因此产生了二义性.
return 0;
}
我们再来看一个函数所在命名空间和函数的类类型参数所在的命名空间都定义了该函数而引起的 ambiguous 的问题:
//example 3
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
namespace NA
{
class TA
{};
void bar(TA &) // 1
{
cout <<"NA::bar()" <<endl;
}
}
void bar(NA::TA &) // 2
{
cout <<"::bar()" <<endl;
}
int main(int argc, char *argv[])
{
NA::TA t;
bar(t); // 这里发生编译错误, 由于 Koenig Lookup 导致 :: 和 NA:: 两个命名空间都被同等地位的查找 bar 函数, 因此产生了二义性.
return 0;
}
参数所在的命名空间和此函数所处的命名空间, 以及此函数所在的所有外层空间都是同一等级. 因此如下 ambiguous 更加隐晦:
// example 4
namespace NA
{
class T{};
void bar(T &) // 1
{
std::cout <<"N::bar()" <<std::endl;
}
}
namespace NB
{
void bar(NA::T &) // 2. 虽然这个 bar 不在 foo 的命名空间内, 但是通过 Koenig Lookup 依然会导致 ambiguous.
{
std::cout <<"NB::bar()" <<std::endl;
}
namespace NC
{
void foo()
{
NA::T t;
bar(t);
}
}
}
总结:
函数调用时:
- 如果函数的参数是类类型, 那么参数所在的类的命名空间也会被加入到当前函数查找的范围内, 且所有参数所引入的命名空间优先级相同.
- 如果在同一级别的范围内存在不止一个可以兼容的调用, 则产生 ambiguous 编译错误. 请看 example 2
两点注意:
- 参数所在的命名空间的外层命名空间不会被加入到函数调用的查找空间中. 请看 example 1
- 所有参数所引入的命名空间的地位是相同的, 因此可能引起 ambiguous. 请看 example 2
- 由参数引入的命名空间与函数所在命名空间, 以及此命名空间之外的上层命名空间同级. 因此可能引起 ambiguous. 请看 example 4