posts - 134,comments - 22,trackbacks - 0

ADL(Koenig) 查找

ADL ,参数相关查找,也称作为 Koenig 查找(以 Andrew Koenig 的名字命名),是指在编译器对无限定域的函数调用进行名字查找时,所应用的一种查找规则。

首先来看一个函数所在的域的分类:

1 :类域(函数作为某个类的成员函数(静态或非静态))

2 :名字空间域

3 :全局域

而 Koenig 查找,它的规则就是当编译器对无限定域的函数调用进行名字查找时,除了当前名字空间域以外,也会把函数参数类型所处的名字空间 加入查找的范围。

ADL 就是为了确保使用类型 X 的对象 x 时能够像使用 X 的成员函数一样简单 (ensure that code that uses an object x of type X can use its nonmember function interface as easily as it can use member functions) 。

根据 ADL 规则,如果非成员函数想和类型一起被使用,应该将它们置于同一个名字空间中。换句话说,和类型 X 放在同一名字空间下的非成员函数,也是 X 接口的一部分。 ( 常见的有 <<or>> 操作符 )

示例:

#include <iostream>

//class N     // 类域

//{

//public :

//     enum E {e1};

//     void f(E)

//     {

//            std::cout << "N::f(N::E) called\n";

//     }

//};

namespace N{   // 名字空间域

       enum E {e1};

       void f(E){

              std::cout << "N::f(N::E) called\n";

       }

}

void f(int)

{

       std::cout << " ::f(int) called\n";

}

 

int main()

{

       ::f(N::e1);     // 受限函数,禁用 ADL  // 输出 ::f(int) called

       f(N::e1);      // 普通查找到 f();

       return 0;

}

 

上述例子,如果 N 为 class ,输出为: ::f(int) called ;如果 N 为名字空间,输出: N::f(N::E) called 。

也就是说 ADL 仅会将参数所在的名字空间中的函数名字加入查找范围。

调用函数时,显式地限定名字空间将禁用 ADL 查找 ,加快解析过程。

 

Argument Dependent Lookup (ADL)解析

ADL,参数相关查找,也称作为Koenig查找(以Andrew Koenig的名字命名,有兴趣可以看Scott Meyer的文章The Most Important C++ People...Ever),是指在编译器对无限定域的函数调用进行名字查找时,所应用的一种查找规则。

f(x, y, z); // unqualified
N::f(x, y, z); // qualified

上面的函数调用,第一个f就是无限定域的函数调用,第二个则限定了在名字空间N里面,也是说使用了完全限定名。
我们首先来看一个函数所在的域的分类:
1:类域(函数作为某个类的成员函数(静态或非静态))
2:名字空间域
3:全局域
而Koenig查找,它的规则就是当编译器对无限定域的函数调用进行名字查找时,除了当前名字空间域以外,也会把函数参数类型所处的名字空间加入查找的范围。
Herb提供的解释(Exceptional C++, Item 31)

Koenig Lookup(simplified): If you supply a function argument of class type (here x, of type A::X), then to look up the correct function name the compiler considers matching names in the namespace (here A) containing the argument's type.

请看下面的例程:
#include <iostream>
usingnamespace std;

namespace Koenig
{
    class KoenigArg
    
{
    public:
          ostream
& print(ostream&out) const
         
{
                 out<<member_<<endl;
          }


          KoenigArg(
int member =5) : member_(member){}

    
private:
         
int member_;
     }
;


     inline ostream
&operator<<(ostream&out, const KoenigArg& kArg)
    
{
         return kArg.print(out);
     }

}


int main()
{
     Koenig::KoenigArg karg(10);
     cout
<<karg;

    
char c;cin>>c;

    
return0;
}

我们通常都会写如上的代码,使用operator<<打印对象的状态,但是ostream& operator<<(ostream& out, const KoenigArg& kArg) 的定义是处于名字空间Koenig,为什么编译器在解析main函数(全局域)里面的operator<<调用时,它能够正确定位到Koenig名字空间里面的operator<<?这是因为根据Koenig查找规则,编译器需要把参数类型KoenigArg所在的名字空间Koenig也加入对operator<<调用的名字查找范围中。

如果没有Koenig查找规则,我们就无法直接写cout<<karg;,而是需要写类似Koenig::operator<<(std::cout, karg); 这样的代码(使用完全限定名)。嗯,即不直观也不方便是吗?更重要的是如果我们写的是模版代码,在模版参数还没有实例化之前,我们根本就不知道参数所处的名字空间,比如:

template<typename T>void print(const T& value)
{
     std::cout<<value;
}


print(karg);
很显然,你的模版代码根本无法确认T是来自那个名字空间,直到编译器对模版实例化(print(karg); 被调用)。
对Koenig查找规则的一个异议是,由于Koenig查找规则的存在,处于某个名字空间的函数调用的重载决议会受到另外一个名字空间的自由函数所影响,仅仅是由于它使用了另外一个名字空间的类型作为参数。在这样的规则下,名字空间看起来不像我们一般所想象的那样是完全封闭和独立的。
我们应该怎么解释这样的异议呢?这样隐讳的影响或者依赖性是合理的吗?Herb认为,如果我们把另外一个名字空间的自由函数(非类成员函数)也看作是它所涉及的类型的接口的一部分,很显然,它应该参与这样的重载决议,这样的跨越名字空间的影响是合理的。从而导出了Herb在传统类定义之上的一个更详细和完整的解释(请参考Exceptional C++, Item 32)。
传统的类定义:

A class describes a set of data, along with the functions that operate on that data.

一个类描述了数据的集合以及操作这些数据的函数。
Herb的类定义,称之为接口准则(Interface Principle):
For a class X, all functions, including free functions, that both
"Mention" X
Are "supplied with" X

are logically part of X, because they form part of the interface of X.

对应类X来说,所有函数,包括自由函数,只要它们
         提及X(跟X有关)
         X一起提供
都在逻辑上被认为是X的一部分,因为它们是X的接口的一部分。
关于Koenig查找,我们该说的都说了吗?其实未然,之前所描述的只是Koenig查找一般可能发生的状况,当Koenig查找规则和C++ 原来的Ordinal Lookup(OL,顺序查找规则)混合在一起的时候,它们之间的组合所产生的状况要比之前的例子复杂的多……
posted on 2009-12-13 11:37 何克勤 阅读(373) 评论(0)  编辑  收藏 所属分类: C/C++

只有注册用户登录后才能发表评论。


网站导航: