194

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

最好我想知道:

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

4 回答 4

253

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

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

当函数调用 (5.2.2) 中的后缀表达式是非限定 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-declaration 也没有 -directive,using但编译器仍然通过应用Koenig lookupdoSomething()将非限定名称正确识别为命名空间中声明的函数。MyNamespace

它是如何工作的?

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

Koenig 查找的优势是什么?

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

为什么要批评 Koenig 查找?

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

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

std::swap(obj1,obj2);

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

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

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

如果存在命名空间A,并且如果A::obj1、、A::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)它会模棱两可。

琐事:

为什么称为“Koenig 查找”?

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

进一步阅读:


** 1 ** Koenig 查找的定义在 Josuttis 的书中定义,*C++ 标准库:教程和参考*。
于 2011-11-13T13:12:02.043 回答
81

在 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程序也看起来很复杂。

于 2011-11-13T13:07:53.860 回答
31

也许最好从为什么开始,然后才去如何。

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

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 查找。

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

于 2011-11-13T13:32:12.790 回答
21

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

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 使用boost::shared_ptr)。

于 2011-11-13T13:33:40.680 回答