*命名空间的出现对于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默默打理好了一切。
<全文完>