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


176

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

最好我想知道:

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




Answers:


223

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.
}

在上面的示例中,既没有using-declaration也没有-directive,using但是编译器仍然可以通过应用Koenig lookup正确地将不合格的名称标识doSomething()为在名称空间中声明的函数。MyNamespace

它是如何工作的?

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

Koenig查找的优点是什么?

如上面的简单代码示例所示,Koenig查找为程序员提供了便利和易用性。如果没有Koenig查找,则程序员将不得不负担重复指定完全限定名称的费用,或者使用大量的using-declaration。

为什么批评Koenig查找?

过度依赖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)模棱两可而无法编译。

琐事:

为什么称为“ Koenig查找”?

因为它是由前AT&T和Bell Labs的研究员兼程序员Andrew Koenig设计的

进一步阅读:


1 Koenig查找的定义在Josuttis的书《 The C ++ Standard Library:A Tutorial and Reference》中定义


11
@AlokSave:答案为+1,但琐事不正确。Koenig并没有发明ADL,因为他在这里承认:)
legends2k 2014年

20
对Koenig算法的批评中的示例可以被视为Koenig查找的“功能”与“骗局”一样多。以这种方式使用std :: swap()是一个常见的习惯用法:如果未提供更专业的版本A :: swap(),请提供“使用std :: swap()”。如果有A :: swap()的专用版本,我们通常希望调用该版本。这可以为swap()调用提供更多通用性,因为我们可以信任该调用进行编译和工作,但是如果有一个调用,我们也可以信任要使用的更专业的版本。
安东尼·霍尔

6
@anthrond还有更多内容。随着std::swap你确实有做到这一点,因为唯一的选择是添加std::swap模板函数显式特为您A的类。但是,如果您的A课程本身是模板,那么它将是部分专业化,而不是显式专业化。并且不允许模板功能的部分专业化。添加重载std::swap是一种替代方法,但明确禁止(您不能在std名称空间中添加内容)。因此,ADL是的唯一方法std::swap
亚当·巴杜拉

1
我本来希望在“ koenig查找的优点”下看到重载运算符。该示例std::swap()似乎有些倒退。我希望问题出在何时std::swap()选择而不是特定于类型的重载A::swap()。该示例std::swap(A::MyClass&, A::MyClass&)似乎具有误导性。因为std永远不会为用户类型带来特定的重载,所以我认为这不是一个很好的例子。
Arvid

1
@gsamaras ...然后呢?我们都可以看到该函数从未定义过。您的错误消息证明它确实有效,因为它正在寻找MyNamespace::doSomething,而不仅仅是::doSomething
基金莫妮卡的诉讼

69

在Koenig Lookup中,如果在未指定函数名称空间的情况下调用函数,则还将在定义了参数类型的名称空间中搜索函数名称。这就是为什么它也称为依赖参数的名称查找的原因,简称为ADL

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

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

否则,我们将不得不写:

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

这真的太多了,代码看起来真的很丑!

换句话说,在没有Koenig查找的情况下,甚至Hello World程序也看起来很复杂。


12
有说服力的例子。
安东尼·霍尔

10
@AdamBadura:请注意,这std::cout是该函数的一个参数,足以启用ADL。你注意到了吗?
Nawaz 2015年

1
@meet:您的问题需要一个很长的答案,这个地方无法提供。因此,我只能建议您阅读以下主题:1)的签名ostream<<(如作为参数的内容和返回的内容)。2)完全限定的名称(如std::vectorstd::operator<<)。3)更详细地研究参数依赖查找。
纳瓦兹

2
@WorldSEnder:是的,您是对的。可以std::endl作为参数的函数实际上是成员函数。无论如何,如果我使用"\n"而不是std::endl,那么我的答案是正确的。感谢您的评论。
纳瓦兹

2
@Destructor:因为形式为f(a,b)的函数调用会调用自由函数。因此,在的情况下std::operator<<(std::cout, std::endl);,没有这样的自由函数std::endl作为第二个参数。它是成员函数,它std::endl以参数为参数,并且您必须为其编写std::cout.operator<<(std::endl);。并且由于有一个自由 函数char const*作为第二个参数,因此"\n"可以工作;'\n'也会工作。
Nawaz

30

也许最好从“为什么”开始,然后再转到“如何”。

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

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

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


谢谢!从来没有真正了解它为什么在那里!
user965369

20

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

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)都可以找到(通过ADL使用)boost::begin可以找到boost::shared_ptr


我一直想知道首先考虑模板参数有什么好处。
2011年

是否可以说仅建议ADL使用ADL,最好为其他功能显式编写名称空间呢?
balki 2013年

它还考虑参数基类的名称空间吗?(如果这样做的话,那会很生气)。
Alex B

3
怎么修?使用std :: begin?
paulm 2014年

2
@paulm是,std::begin清除名称空间的歧义。
Nikos '18
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.