*命名空间的出现对于C++的影响是非常大的,比如说using声明和using指令或者使用namespace作用域加以限定的名字。

还记得自己阅读的第一份源码是Laurent Gomila写的SFML游戏引擎 的源代码,阅读的第一份源码居然如此优美实在感到非常幸运。不过其中对于namespace的使用也让当时青涩稚嫩的我大呼“卧槽屌炸了!”(注,这篇博客写于2015年3月,当年青涩稚嫩的我现在依然青涩单纯可爱……)

命名空间也有很蛋疼的但是很有用的用途,ADL(Argument Dependent Lookup)就是其中之一。*

namespace 名字空间

回想起当年学Haskell的时候,看edx上的lecture,印象最深的一句话就是:Giving name is hard for programmers.

不同的人可能给他自己写的类或函数中都有相同的名字,比方说如果有两个库,两个库中都有自定义的String类,那就往往会造成冲突。

所以,namespace提供了解决这个问题的办法。举个例子,一个村有两个小明,一个住村口,一个住村里,那么我们为了区分两个小明,往往会使用“村口的小明”或“村里的小明”进行区分。那么“村口”以及“村里”就可以被认为是命名空间了。

所以本质上,namespace是对全局作用域的细分。如下面的代码:

namespace org_semantics { 
    class String (...) 
    String operator + ( const String &, const String &);
    ... 
}

这样一来就是将String放到命名空间org_semantics中,如果我们需要使用这个String,我们需要使用org_semantics::String来进行调用。

在中山大学Sicily中混迹的我们都习惯于使用using namespace ...代码刷题,但是在项目中要记得尽量避免这类代码,这种代码风格不好。

Argument Dependent Lookup(ADL)

何为ADL?ADL的思想其实很简单:当编译器查找函数调用表达式的函数名字的时候,它会到所谓的“包含函数调用实参的类型”的名字空间中检查。

考虑如下代码:

namespace org_semantics{ 
    class X {...}; 
    void f (const X& ); 
    void g (X * ); 
    X operator + ( const X &, const X &); 
    class String {...}; 
}
int g ( org_semantics::X *);
void aFunc() 
{ 
    org_semantics::X a ; 
    f(a ); // 调用 org_semantics::f 
    g (&a ); // 错误,歧义了 
}

在普通的函数查找中,是找不到org_semantics::f的,因为它被放在命名空间中。但是由于实参的类型是在命名空间中被定义的,因此编译器也会在那里去找候选的函数,这也就是f(a)能够成功调用的原因。

当然,对于函数g来说,它的调用会发生错误。开发人员应该是想要调用全局的g函数,但是由于实参的类型是org_semantics ::X *,因此命名空间中的g函数也成为了候选函数。

这往往会让开发者纠结,但是也未尝一定就是坏事——说不定命名空间中已经提供了这个功能的函数,编译器报错了提醒程序员有这个功能也说不定呢。

值得注意的是,哪怕对g的调用导致了两个候选函数参与重载解析,全局作用于的g其实也没有重载org_semantics::g,因为它们不再同一个作用域。换句话说,ADL是关于函数是如何被调用的,而重载是关于函数如何被声明的,两者不在一个频道。

最后的一句a = a + a,同样的,编译器会在命名空间中查找+操作符,所以ADL在重载操作符里面也发挥了作用。

值得一提的是,很多程序员广泛使用了ADL却没有意识到这一点,比如下面这一段代码:

org_semantics::String name ("金坷拉");
std::cout << "Hello, " << name;

在上面的代码中,第一个操作符<<很有可能调用的是std::basic_ostream的一个成员函数,而第二个则是位于命名空间的重载的operator <<的非成员函数的调用,这些细节对于这段代码的编写者来说可能注意不到,但是实际上ADL默默打理好了一切。

<全文完>