最好能够了解以下内容:
- 它为什么是好事? - 它为什么是坏事? - 它是如何工作的?
Koenig查找,或称为参数依赖查找,描述了C++中编译器如何查找未限定名称。
C++11标准§3.4.2/1规定:
当函数调用(5.2.2)中的后缀表达式是未限定标识符时,在常规未限定查找(3.4.1)期间未考虑的其他命名空间可能会被搜索,并且在这些命名空间中,可以找到命名空间作用域友元函数声明(11.3),否则不可见。这些搜索的修改取决于参数的类型(对于模板模板参数,还取决于模板参数的命名空间)。
用更简单的话来说,Nicolai Josuttis指出1:
如果一个或多个参数类型在函数的命名空间中定义,则不必为函数限定命名空间。
下面是一个简单的代码示例:
namespace MyNamespace
{
class MyClass {};
void doSomething(MyClass) {}
}
MyNamespace::MyClass obj; // global object
int main()
{
doSomething(obj); // Works Fine - MyNamespace::doSomething() is called.
}
using
声明也没有using
指令,但编译器仍然通过应用Koenig查找正确地将未限定名称doSomething()
识别为命名空间MyNamespace
中声明的函数。
该算法告诉编译器不仅要查看本地作用域,还要查看包含参数类型的命名空间。因此,在上述代码中,编译器发现函数doSomething()
的参数obj
属于命名空间MyNamespace
。因此,它查看该命名空间以定位doSomething()
的声明。
正如上面的简单代码示例所示,Koenig查找为程序员提供了方便和易用性。如果没有Koenig查找,程序员将需要重复指定完全限定名称,或者使用大量using
声明。
过度依赖Koenig查找可能会导致语义问题,并有时使程序员措手不及。
考虑std::swap
的示例,它是一个标准库算法,用于交换两个值。使用Koenig查找时,使用此算法时必须小心,因为:
std::swap(obj1,obj2);
可能表现不同于:
using std::swap;
swap(obj1, obj2);
使用ADL,调用哪个swap
函数取决于传递给它的参数的命名空间。
如果存在一个命名空间A
,并且如果A::obj1
,A::obj2
和A::swap()
存在,则第二个示例将导致调用A::swap()
,这可能不是用户想要的。
此外,如果由于某种原因同时定义了A::swap(A::MyClass&, A::MyClass&)
和std::swap(A::MyClass&, A::MyClass&)
,则第一个示例将调用std::swap(A::MyClass&, A::MyClass&)
,但第二个示例将无法编译,因为swap(obj1, obj2)
会产生歧义。
因为它是由前AT&T和贝尔实验室研究员和程序员Andrew Koenig设计的。
标准C++03/11 [basic.lookup.argdep]:3.4.2参数相关的名称查找。
std::swap
时,你必须这样做,因为唯一的选择是为你的 A
类添加 std::swap
模板函数的显式特化。但是,如果你的 A
类本身就是一个模板,则它将是部分特化而不是显式特化。而且,模板函数的部分特化是不允许的。添加 std::swap
的重载可能是一个替代方案,但明确禁止这样做(你不能向 std
命名空间添加东西)。因此,ADL 是 std::swap
的唯一方法。 - Adam Badurastd::swap()
的例子似乎有些反向。我认为问题应该出现在选择std::swap()
而不是特定于类型的重载A::swap()
时。使用std::swap(A::MyClass&, A::MyClass&)
的例子似乎是具有误导性的。由于std
永远不会针对用户类型进行特定的重载,因此我认为这不是一个很好的例子。 - Arvidprog.cpp:(.text.startup+0x7):对
MyNamespace::doSomething(MyNamespace::MyClass)' 的引用未定义。` - gsamarasMyNamespace :: doSomething
,而不仅仅是::doSomething
。 - anonstd::cout << "Hello World!" << "\n";
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
这实在是太繁琐了,代码看起来也很丑陋!
换句话说,在没有Koenig Lookup的情况下,即使是一个Hello World程序看起来也很复杂。
std::cout
是函数的一个参数,这已足以启用 ADL。你有注意到吗? - Nawazostream<<
的签名(即它接受什么参数和返回什么)。2)完全限定名称(如 std::vector
或 std::operator<<
)。3)更详细的论述参数依赖查找。 - Nawazstd::operator<<(std::operator<<(std::cout, s), std::endl);
应该改为 std::operator<<(std::cout, s).operator<<(std::endl);
, 参见 http://ideone.com/FFKA7b - WorldSEnderstd::endl
作为参数传递的函数实际上是一个成员函数。无论如何,如果我使用"\n"
代替std::endl
,那么我的答案就是正确的。感谢您的评论。 - Nawazf(a,b)
的函数调用会调用一个自由函数。因此,在 std::operator<<(std::cout, std::endl);
的情况下,没有接受 std::endl
作为第二个参数的自由函数。它是接受 std::endl
作为参数的成员函数,你需要编写 std::cout.operator<<(std::endl);
。而且,由于有一个接受 char const*
作为第二个参数的自由函数,所以 "\n"
可以工作;'\n'
也可以工作。 - Nawaznamespace N
{
class X {};
void f(X);
X& operator++(X&);
}
int main()
{
// define an object of type X
N::X x;
// apply f to it
N::f(x);
// apply operator++ to it
???
}
当然,你可以写成N::operator++(x)
,但这样做将会破坏重载运算符的全部意义。因此需要找到一种解决方案,使得编译器能够在不在作用域内的情况下找到operator++(X&)
。另一方面,它仍然不应该找到在另一个不相关的命名空间中定义的另一个operator++
,这可能会导致调用的不确定性(在这个简单的例子中,你不会遇到不确定性,但在更复杂的例子中,你可能会)。解决办法是参数相关查找(ADL),因为查找取决于参数(更确切地说,取决于参数的类型)。由于该方案是由Andrew R. Koenig发明的,因此也经常被称为Koenig查找。x++
,它会在全局作用域中查找operator++
,同时还会在定义x
的类型N::X
所在的作用域中查找,即在namespace N
中查找。然后它找到了匹配的operator++
,因此x++
就可以正常工作了。然而,另一个在另一个命名空间中定义的operator++
(比如N2
)将不会被找到。由于ADL不限于命名空间,你也可以在main()
中使用f(x)
代替N::f(x)
。在我看来,并不是所有关于它的东西都是好的。人们,包括编译器供应商,由于它的有时不幸的行为而对它进行了侮辱。
ADL 对 C++11 中的 for-range 循环进行了重大改革。为了理解为什么 ADL 有时会产生意外的影响,请考虑不仅考虑参数定义的命名空间,还要考虑参数模板参数的参数、函数类型/指针类型的参数类型/指针类型的参数类型等等。
一个使用 boost 的示例
std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);
如果用户使用boost.range库,将会导致一个歧义性问题,因为ADL(关联依赖查找)会同时找到std::begin
(使用std::vector
)和boost::begin
(使用boost::shared_ptr
)。
boost::begin
并在 namespace boost
中替换为 using std::begin
。Boost 中所有已添加到 std
的内容都需要从 namespace boost
中删除,只留下 using std::whatever
。 - Chris Dodd
std::cout << "Hello world";
将无法编译。 - sehe