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

时间:2011-11-13 12:58:44

标签: c++ argument-dependent-lookup name-lookup c++-faq

对于什么参数依赖查找有什么好的解释?许多人也称它为Koenig Lookup。

我最好知道:

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

4 个答案:

答案 0 :(得分:191)

Koenig Lookup Argument 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.
}

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

它是如何运作的?

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

Koenig查找的优势是什么?

正如上面的简单代码示例所示,Koenig查找为程序员提供了方便和易用性。如果没有Koenig查找,程序员就会有开销,重复指定完全限定名称,或者使用大量doSomething() - 声明。

为什么批评Koenig查找?

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

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

using

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

std::swap(obj1,obj2);

使用ADL,调用哪个版本的using std::swap; swap(obj1, obj2); 函数取决于传递给它的参数的名称空间。

如果存在名称空间swap以及AA::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&)将不明确

花絮:

为什么称它为“Koenig查找”?

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

进一步阅读:


<子> 1 Koenig查找的定义如Josuttis的书中所定义, The C ++ Standard Library:A Tutorial and Reference

答案 1 :(得分:58)

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

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

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

否则,我们必须写:

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

打字太多了,代码看起来真的太丑了!

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

答案 2 :(得分:26)

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

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

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++,这可能会使调用变得模糊(在这个简单的例子中,你不会产生歧义,但在更复杂的例子中,你可能会) 。解决方案是Argument Dependent Lookup(ADL),这种方式称为,因为查找依赖于参数(更准确地说,取决于参数的类型)。由于该计划是由Andrew R. Koenig发明的,因此通常也称为Koenig查找。

诀窍在于,对于函数调用,除了正常的名称查找(在使用点查找范围内的名称)之外,还会在给予函数的任何参数的类型范围内进行第二次查找。因此,在上面的示例中,如果您在main中编写x++,它不仅会在全局范围内查找operator++,还会在x,{{1}的类型范围内查找N::X },已定义,即在namespace N中。并且它找到了匹配的operator++,因此x++才有效。但是,将找不到另一个名称空间中定义的另一个operator++,例如N2。由于ADL不仅限于名称空间,因此您还可以在f(x)中使用N::f(x)代替main()

答案 3 :(得分:19)

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

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

使用boost

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

如果用户使用boost.range库,则会导致歧义,因为找到了std::begin(通过ADL使用std::vector)并找到boost::begin(通过ADL使用{ {1}})。