什么是“基于参数的查询”(又名ADL或“Koenig Lookup”)?
什么是依赖于参数的查找的一些很好的解释? 许多人也称之为Koenig Lookup。
最好我想知道:
- 为什么这是件好事?
- 为什么这是一件坏事?
- 它是如何工作的?
(注意:这意味着要成为Stack Overflow的C ++ FAQ的一个入口。)
Koenig Lookup在C ++中通常也被称为参数依赖查找 ( Argument Dependent Lookup) ,大多数标准C ++编译器都支持它。
C ++ 11标准§3.4.2 / 1指出:
当函数调用(5.2.2)中的postfix-expression是一个非限定id时,可以search在通常的非限定查找(3.4.1)期间不考虑的其他命名空间,并且在这些命名空间中,命名空间范围的好友函数声明11.3)不可见的可能被发现。 对search的这些修改取决于参数的types(以及模板参数,模板参数的名称空间)。
简单来说,Nicolai Josuttis说1 :
如果在函数的名称空间中定义了一个或多个参数types,则不必限定函数的名称空间。
一个简单的代码示例:
namespace MyNamespace { class MyClass {}; void doSomething(MyClass); } MyNamespace::MyClass obj; // global object int main() { doSomething(obj); // Works Fine - MyNamespace::doSomething() is called. }
在上面的例子中,既没有using-declaration
也没有using-directive
但是编译器仍然MyNamespace
通过应用Koenigalgorithm,正确地将非限定名称doSomething()
作为名称空间MyNamespace
声明的函数进行MyNamespace
。
它是如何工作的?
该algorithm告诉编译器不只是查看本地范围,而是查看包含参数types的名称空间。 因此,在上面的代码中,编译器发现作为函数doSomething()
的参数的obj
对象属于命名空间MyNamespace
。 因此,它查看该名称空间以查找doSomething()
的声明。
Koenig Lookup
什么优势?
正如上面简单的代码示例所示,Koenigalgorithm为程序员提供了便利和易用性。 如果没有Koenigalgorithm,编程人员会花费一些开销,反复指定完全限定的名称,或者改为使用大量的使用声明。
为什么批评Koenig Algorithm
?
过分依赖Koenigalgorithm可能导致语义问题,有时候会让程序员无法自拔。
考虑std :: swap的例子,这是一个交换两个值的标准库algorithm。 使用Koenigalgorithm时,使用这种algorithm时必须小心谨慎,因为:
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)
将是不明确的。
琐事:
为什么叫Koenig Lookup
?
因为它是由前AT&T和贝尔实验室的研究员和程序员Andrew Koenigdevise的 。
好读:
草药萨特的名字查找GotW
标准C ++ 03/11 [basic.lookup.argdep]:3.4.2参数相关名称查找。
1 Koenigalgorithm的定义在Josuttis的书“ The C ++ Standard Library:A Tutorial and Reference”中定义 。
在Koenig Lookup中,如果一个函数被调用时没有指定它的名称空间,那么函数的名字也是在定义了参数types的名字空间中search的。 这就是为什么它也被称为参数 – 依赖名称查询 ,简而言之简单的ADL 。
这是因为Koenig Lookup,我们可以这样写:
std::cout << "Hello World!" << "\n";
否则,在Koenig Lookup缺乏的情况下,我们必须写下:
std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
这是真的太多打字,代码看起来非常丑陋!
换句话说,在没有Koenig Lookup的情况下,即使Hello World程序看起来也很复杂。
也许最好是从为什么开始,然后才去做。
引入名称空间时,想法是在名称空间中定义一切,这样单独的库不会相互干扰。 但是,这引起了运营商的一个问题。 看下面的代码示例:
namespace 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)
,但是这样会打败运算符的重载。 因此,必须find一个解决scheme,它允许编译器findoperator++(X&)
尽pipe它不在范围内。 另一方面,它仍然不应该find另一个operator++
定义在另一个不相关的命名空间,这可能会导致调用模糊(在这个简单的例子中,你不会有歧义,但在更复杂的例子中,你可能)。 该解决scheme是参数依赖查找(ADL),因为查找取决于参数(更确切地说,参数的types),所以这样调用。 由于该scheme是由安德鲁·R·科尼格(Andrew R. Koenig)发明的,因此也常被称为科尼希(Koenig)查找。
诀窍在于,对于函数调用,除了普通名称查找(在使用点查找范围内的名称)外,还会在给函数的任何参数types的范围内进行第二次查找。 因此,在上面的例子中,如果你在main中编写x++
,它不仅在全局范围内查找operator++
,而且在定义了x
, N::X
的types的范围内(即在namespace N
查找operator++
。 在那里它find了一个匹配的operator++
,因此x++
正常工作。 但另一个名称空间中定义的operator++
N2
)将不会被find。 由于ADL不限于命名空间,因此您也可以在main()
使用f(x)
而不是N::f(x)
main()
。
在我看来,并非所有的事情都是好的。 包括编译器供应商在内的人们由于其有时不幸的行为而侮辱了它。
ADL负责对C ++ 11中的范围循环进行重大改进。 要理解为什么ADL有时可能会产生意想不到的效果,请考虑不仅仅是参数定义的名称空间,还有参数的模板参数的参数,函数types的参数types/这些参数的指针types的指针types等等。
一个使用boost的例子
std::vector<boost::shared_ptr<int>> v; auto x = begin(v);
如果用户使用boost.range库,会导致模糊,因为std::begin
(通过ADL使用std::vector
)和boost::begin
(通过使用boost::shared_ptr
ADL)都被find。