什么是"Argument-Dependent Lookup"(又名ADL或"Koenig Lookup")?

use*_*369 163 c++ c++-faq name-lookup argument-dependent-lookup

什么是关于什么参数依赖查找的好解释?很多人也称它为Koenig Lookup.

我最好知道:

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

Alo*_*ave 208

Koenig LookupArgument Dependent Lookup描述了编译器在C++中如何查找非限定名称.

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

当函数调用(5.2.2)中的postfix-expression是非限定id时,可以搜索在通常的非限定查找(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.
}
Run Code Online (Sandbox Code Playgroud)

在上面的示例中既没有using-declaration也没有-directive,using但编译器仍然通过应用Koenig查找正确地将非限定名称标识doSomething()为在命名空间中声明的函数.MyNamespace

它是如何工作的?

该算法告诉编译器不仅要查看本地范围,还要查看包含参数类型的命名空间.因此,在上面的代码中,编译器发现obj作为函数参数的对象doSomething()属于命名空间MyNamespace.因此,它查看该命名空间以查找声明doSomething().

Koenig查找的优势是什么?

正如上面的简单代码示例所示,Koenig查找为程序员提供了便利和易用性.如果没有Koenig查找,程序员就会有一个开销,要重复指定完全限定的名称,或者使用多个using-declarations.

为什么批评柯尼希查找?

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

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

std::swap(obj1,obj2);
Run Code Online (Sandbox Code Playgroud)

可能不会显示与以下相同的行为:

using std::swap;
swap(obj1, obj2);
Run Code Online (Sandbox Code Playgroud)

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

如果存在命名空间A并且if A::obj1,A::obj2&exists 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查找"?

因为它是由前AT&T和贝尔实验室研究员兼程序员Andrew Koenig设计的.

进一步阅读:


1 Koenig查找的定义如Josuttis的书 "C++标准库:教程和参考"中所定义.

  • 批评Koenig算法的例子可以被认为是Koenig查找的"特征"和"con"一样多.以这种方式使用std :: swap()是一个常见的习惯用法:如果没有提供更专业的版本A :: swap(),请提供'using std :: swap()'.如果A :: swap()的专用版本可用,我们会想要*那个被调用的版本.这为swap()调用提供了更多的通用性,因为我们可以信任编译和工作的调用,但是如果有的话,我们也可以信任更专业的版本. (18认同)
  • @AlokSave:答案为+1,但琐事不正确.Koenig没有发明ADL,因为[他在这里承认](http://www.drdobbs.com/cpp/a-personal-note-about-argument-dependent/232901443):) (9认同)
  • @anthrond还有更多内容.使用`std :: swap`你实际上必须这样做,因为唯一的选择是为你的`A`类添加`std :: swap`模板函数显式特化.然而,如果你的`A`类本身就是一个模板,它将是部分特化而不是显式专业化.并且不允许模板功能的部分特化.添加`std :: swap`的重载将是一种替代方法,但是被明确禁止(你可能不会向`std`命名空间添加内容).所以ADL是`std :: swap`的唯一方式. (4认同)
  • 我本来希望在“koenig 查找的优势”下看到重载运算符的提及。带有 ``std::swap()`` 的例子似乎有点倒退。我希望问题是在选择了 ``std::swap()`` 而不是特定于类型的重载 ``A::swap()`` 时。带有 ``std::swap(A::MyClass&, A::MyClass&)`` 的示例似乎具有误导性。因为 ``std`` 永远不会有用户类型的特定重载,我认为这不是一个很好的例子。 (2认同)

Naw*_*waz 62

在Koenig Lookup中,如果在未指定其名称空间的情况下调用函数,则还会在名称空间中搜索函数的名称,在该名称空间中定义了参数的类型.这就是为什么它也被称为Argument-Dependent name Lookup,简称ADL.

这是因为Koenig Lookup,我们可以这样写:

std::cout << "Hello World!" << "\n";
Run Code Online (Sandbox Code Playgroud)

否则,我们必须写:

std::operator<<(std::operator<<(std::cout, "Hello World!"), "\n");
Run Code Online (Sandbox Code Playgroud)

这真的是太多的打字和代码看起来真的很难看!

换句话说,在没有Koenig Lookup的情况下,即使是Hello World程序看起来也很复杂.

  • 有说服力的例子. (9认同)
  • @AdamBadura:请注意,`std :: cout`是函数的一个参数,足以启用ADL.你注意到了吗? (7认同)
  • @WorldSEnder:是的,您是对的。可以将“ std :: endl”作为参数的函数实际上是成员函数。无论如何,如果我使用“ \ n”代替“ std :: endl”,那么我的答案是正确的。感谢您的评论。 (2认同)
  • @Destructor:因为形式为f(a,b)的函数调用会调用* free *函数。因此,在`std :: operator &lt;&lt;(std :: cout,std :: endl);`的情况下,没有这样的自由函数将`std :: endl`作为第二个参数。它是成员函数,以`std :: endl`作为参数,并且必须为此编写`std :: cout.operator &lt;&lt;(std :: endl);`。并且由于有一个* free *函数将char const *作为第二个参数,因此“ \ n”有效;`'\ n'`也可以。 (2认同)

cel*_*chk 29

也许最好先从为什么开始,然后再去了解如何.

引入名称空间时,想法是在名称空间中定义所有内容,以便单独的库不会相互干扰.然而,这引入了运营商的问题.请查看以下代码示例:

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
  ???
}
Run Code Online (Sandbox Code Playgroud)

当然你可以写N::operator++(x),但这会打败运营商重载的整个过程.因此,必须找到一个解决方案,它允许编译器找到operator++(X&)它,尽管它不在范围内.另一方面,它仍然不应该operator++在另一个无关的命名空间中找到另一个定义,这可能会使调用变得模糊(在这个简单的例子中,你不会产生歧义,但在更复杂的例子中,你可能会).解决方案是Argument Dependent Lookup(ADL),这种方式称为,因为查找依赖于参数(更准确地说,取决于参数的类型).由于该计划是由Andrew R. Koenig发明的,因此通常也称为Koenig查找.

诀窍是,对于函数调用,除了正常的名称查找(在使用点查找范围内的名称)之外,还会在给予函数的任何参数的类型范围内进行第二次查找.所以在上面的例子,如果你写x++在主,它看起来operator++不仅在全球范围内,但附加在类型范围x,N::X在,被定义,即namespace N.在那里它找到匹配operator++,因此x++只是工作.另一个operator++在另一个空间中定义的,比方说N2,将不会被发现然而,.由于ADL不限于命名空间,因此您也可以使用f(x)而不是N::f(x)in main().


Joh*_*itb 20

在我看来,并非一切都很好.人们,包括编译器供应商,一直在侮辱它,因为它有时是不幸的行为.

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

使用boost的示例

std::vector<boost::shared_ptr<int>> v;
auto x = begin(v);
Run Code Online (Sandbox Code Playgroud)

如果用户使用boost.range库,则会导致歧义,因为两者都std::begin被找到(通过ADL使用std::vector)并且boost::begin被找到(通过ADL使用boost::shared_ptr).

  • 怎么修?使用std :: begin? (2认同)