什么是“依赖参数的查找”;(又名ADL,或“Koenig查找”)?

什么是参数依赖查找?许多人也称它为Koenig Lookup。

最好我想知道:

  • 为什么这是一件好事?
  • 为什么这是一件坏事?
  • 它是如何工作的?
36761 次浏览

在Koenig Lookup中,如果调用函数时没有指定其命名空间,则函数名将在定义实参类型的命名空间中搜索。这就是为什么它也被称为依赖参数的名称查找,简而言之就是诽谤联盟

正是由于Koenig Lookup,我们可以这样写:

std::cout << "Hello World!" << "\n";

否则,我们将不得不这样写:

std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");

这真的是太多的输入和代码看起来真的很难看!

换句话说,在没有Koenig Lookup的情况下,即使是你好世界程序看起来也很复杂。

Koenig查找参数依赖查找 .描述了c++中编译器如何查找非限定名称。

c++ 11标准§3.4.2/1规定:

当函数调用(5.2.2)中的后缀表达式是一个非限定id时,可能会搜索其他在通常的非限定查找(3.4.1)中没有考虑到的名称空间,并且在这些名称空间中,可能会找到其他不可见的名称空间作用域友函数声明(11.3)。对搜索的这些修改取决于参数的类型(对于模板模板参数,则取决于模板的名称空间) 论点)。< / p >

简而言之,尼科莱·乔苏蒂的国家:

如果函数的名称空间中定义了一个或多个参数类型,则不必为函数限定名称空间。

一个简单的代码示例:

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,但是编译器仍然通过应用Koenig查找正确地将非限定名称doSomething()识别为命名空间MyNamespace中声明的函数。

它是如何工作的?

该算法告诉编译器不仅要查看局部作用域,还要查看包含参数类型的名称空间。因此,在上面的代码中,编译器发现对象obj,它是函数doSomething()的参数,属于命名空间MyNamespace。因此,它查看该名称空间来定位doSomething()的声明。

Koenig查找的优点是什么?

正如上面的简单代码示例所演示的,Koenig查找为程序员提供了方便和易于使用的功能。如果没有Koenig查找,程序员就会重复指定完全限定名,或者相反,使用大量using-declarations。

为什么要批评柯尼格查找?

过度依赖Koenig查找会导致语义问题,有时会让程序员措手不及。

考虑std::swap的例子,它是交换两个值的标准库算法。使用Koenig查找时,在使用此算法时必须谨慎,因为:

std::swap(obj1,obj2);

可能不会表现出相同的行为:

using std::swap;
swap(obj1, obj2);

在ADL中,调用swap函数的哪个版本取决于传递给它的参数的名称空间。

如果存在命名空间A,并且存在A::obj1A::obj2A::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设计的。

进一步阅读:


**1** Koenig查找的定义在Josuttis的书中定义,* c++标准库:教程和参考*。

也许最好先说为什么,然后再说怎么做。

当引入名称空间时,我们的想法是将所有内容都定义在名称空间中,这样独立的库就不会相互干扰。然而,这给运营商带来了一个问题。请看下面的代码:

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),但这将破坏操作符重载的全部意义。因此,必须找到一个解决方案,允许编译器找到operator++(X&),尽管它不在作用域内。另一方面,它仍然不应该找到另一个operator++定义在另一个不相关的命名空间,这可能会使调用产生歧义(在这个简单的例子中,你不会得到歧义,但在更复杂的例子中,你可能会)。解决方案是参数依赖查找(ADL),这样称呼是因为查找依赖于参数(更确切地说,依赖于参数的类型)。由于该方案是由Andrew R. Koenig发明的,所以它也经常被称为Koenig查找。

诀窍在于,对于函数调用,除了正常的名称查找(在使用点的作用域中查找名称)之外,还在给函数的任何参数的类型的作用域中进行第二次查找。所以在上面的例子中,如果你在main中编写x++,它不仅会在全局作用域中查找operator++,而且还会在定义了xN::X类型的作用域中查找,即在namespace N中。在那里它找到了一个匹配的operator++,因此x++就可以工作了。然而,在另一个命名空间中定义的另一个operator++,例如N2,将不会被找到。由于ADL不局限于名称空间,你也可以在operator++1中使用f(x)而不是operator++0。

在我看来,并不是所有的东西都是好的。人们,包括编译器供应商,一直在侮辱它,因为它有时会有不幸的行为。

ADL负责对c++ 11中的for-range循环进行重大修改。要理解为什么ADL有时会产生意想不到的影响,不仅要考虑定义实参的名称空间,还要考虑实参的模板实参的实参,这些实参的函数类型的形参类型/指针类型的指针类型的形参类型,等等。

一个使用boost的例子

std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);

如果用户使用了boost,这将导致模糊性。因为既找到了std::begin(通过ADL使用std::vector),也找到了boost::begin(通过ADL使用boost::shared_ptr)。