使用std命名空间
在C++中,使用using
关键字有两种方式:使用using-declaration和using-directive。其中,using-declaration是使用using
关键字后跟具体的类型,如using std::vector;
;而using-directive是使用using namespace
关键字后跟命名空间的名称,如using namespace std;
。
对于using-directive,如果不是在头文件的全局作用域中使用,一般来说是没有问题的。因此,在.cpp文件中使用using namespace std;
并不是一个问题,而且如果出现问题,也可以通过限定作用域或者选择特定的代码块来解决。在代码中加入大量的std::
限定符只会增加视觉噪声,所以我认为没有必要。然而,如果在代码中没有大量使用std
命名空间中的名称,那么省略using-directive也是可以的。这是一个自明的事实,如果不需要using-directive,那就没有必要使用它。
类似地,如果只需要使用std
命名空间中的几个特定类型,而不是整个命名空间,那么只需要使用一些using-declaration来引入这些特定的名称即可。相反,如果需要使用25或30个using-declaration,那么使用一个using-directive会更方便。
需要注意的是,有时必须使用using-declaration。例如,在Scott Meyers的《Effective C++》(第三版)的"Item 25: Consider support for a non-throwing swap"中提到,为了使泛型模板函数能够使用参数化类型的最佳swap方法,需要使用using-declaration和参数相关查找(Argument Dependent Lookup,简称ADL或Koenig查找):
templatevoid foo(T& x, T& y) { using std::swap; // 在该函数中使std::swap可用 // 其他操作... swap(x, y); // 如果存在T-specific swap(),则使用它,否则使用std::swap () // ... }
我们可以参考其他大量使用命名空间的语言的常见用法。例如,Java和C#在很大程度上使用命名空间(可以说比C++更多)。在这些语言中,最常见的使用命名空间中的名称的方法是使用等效的using-directive将它们带入当前作用域。这并不会引起大范围的问题,只有在需要处理特定名称时,才需要使用完全限定的名称或者使用别名,这与C++中的处理方式是一样的。
在Herb Sutter和Andrei Alexandrescu的《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》的"Item 59: Don't write namespace usings in a header file or before an #include"中,他们这样说道:
简而言之:在编写完
#include
指令后,在实现文件中可以自由使用namespace using声明和指令,并且可以放心使用。尽管有人多次声称相反,但是namespace using声明和指令并不是邪恶的,也不会破坏命名空间的目的。相反,它们是使命名空间可用的关键。
Stroustrup在《The C++ Programming Language, Third Edition》中曾说过“不要污染全局命名空间”,但他在第C.10.1章中提到:
使用using-declaration可以将名称添加到本地作用域中,而using-directive不会;它只是使得声明的名称在其所在的作用域中可访问。例如:
namespace X { int i, j, k; } int k; void f1() { int i = 0; using namespace X; // 使得X中的名称可访问 i++; // 本地i j++; // X::j k++; // 错误:X::k还是全局k? ::k++; // 全局k X::k++; // X的k } void f2() { int i = 0; using X::i; // 错误:f2()中i已经声明过 using X::j; using X::k; // 隐藏了全局k i++; j++; // X::j k++; // X::k }
本地声明的名称(通过普通声明或者using-declaration声明)会隐藏非本地的相同名称的声明,并且任何非法的重载会在声明处检测到。注意
f1()
中k++
的歧义错误。全局名称并不优先于在全局作用域中可访问的命名空间中的名称。这提供了对意外名称冲突的显著保护,并且重要的是,确保懒惰使用全局名称的人与不污染全局作用域的人没有任何优势。当通过using-directive将声明许多名称的库引入可访问的时候,未使用名称的冲突被视为错误是一个重要的优势。
...
我希望看到新程序在使用命名空间时与传统的C和C++程序相比,对全局名称的使用显著减少。命名空间的规则特别设计得不会给懒惰使用全局名称的人带来任何优势,而是给那些不污染全局作用域的人带来方便。
那么,如何像“懒惰使用全局名称”的人一样获得相同的优势呢?通过利用using-directive,可以在当前作用域中安全地将命名空间中的名称引入,而不会污染全局命名空间。需要注意的是,使用正确的方式将std命名空间中的名称引入到作用域中(将using-directive放在#include指令之后),并不会污染全局命名空间。它只是使得这些名称更容易使用,并且继续保护免受冲突的影响。
关于最后一点:Java和C#也有更整洁的命名空间。如果BCL中的所有内容都位于System命名空间中,“using System”将会引起和“using namespace std”一样的问题。
但是,我看到的Java和C#程序通常会引入它们使用的所有命名空间,而不仅仅是“System”(或其等效的)。因此,不仅仅是一个using-directive可以引入所有使用的名称,而是有5到10个using-directive可以做更多或更少相同的事情。此外,“using namespace std;”会引起那么多问题吗?
问题在于std命名空间中有太多的常用名称,并且包含一个标准头文件可能会引入所有其他头文件。我们对于导入了什么没有很好的控制,存在太多的风险。我对Java和C#了解得不够多,但是我了解Ada,它比C++具有更好的模块系统,在那里导入名称通常是不被看好的。这首先是一个命名约定的问题(我见过人们使用前缀和命名空间,而不导入名称是没有意义的),然后是风格的问题。
我仍然不相信这是一个实际存在的问题。我经常看到使用using-directive而没有严重问题的情况。然而,我也不反对不使用它们。我只是更喜欢代码中没有std::
限定符的情况,而有其他方法可以避免它(使用using-declaration或者typedef通常可以解决问题)。
你能接受我的观点吗?我花了很多时间移除using-declaration,因为它们引发了冲突问题。在Lisp解释器中,list是一个自然的标识符用于表示列表。现在,如果你使用的命名约定能够自然地避免与标准库的冲突(比如类型名称以大写字母开头...),那么你当然不会遇到问题。你很幸运,C++0X已经放弃了concepts,所以不会有这样的名称问题了...
- 我当然接受你的观点,如果它给你带来了问题。然而,我保留固执地坚持我的观点的权利,即使它是错误的 : )。引用巴特·辛普森(Bart Simpson)的话,我不能承诺尝试停止不必要地将“using namespace std;”抛到我的代码中,但我会尝试尝试。
- 你说,“list是一个自然的标识符用于表示Lisp解释器中的列表” - 但是,使用“using namespace std;”并不会阻止你声明你的自然标识符“list”,只是如果这样做,你就不能在不限定的情况下使用std::list。这与没有“using namespace std;”指令没有区别。或者我理解错了吗?
- 我修改了我的回答,解释了发生的情况。故事太长,无法在评论中完成。
在使用C++的标准库时,某些情况下了"Using std Namespace"这个问题。这个问题的出现原因是,在使用标准库的对象和函数时,需要在它们的前面加上"std::",这样就会增加代码的冗余度。另外,如果不使用"using namespace std",就减少了与其他可能存在的命名冲突的机会。
然而,需要注意的是,在头文件中不应该放置"using namespace std",因为它会传播到所有包含该头文件的文件中,即使它们不希望使用该命名空间。
在某些情况下,使用"using std::swap"等语句是非常有益的。如果存在特定版本的swap函数,编译器就会使用该版本,否则会回退到"std::swap"。如果调用"std::swap",则始终使用基本版本,不会调用优化版本(如果存在)。
另外,还某些情况下了"using namespace"的传播问题。需要注意的是,它也可能会影响到正确构造的头文件,只要这些头文件在一个不正确的头文件之后被包含进来。
然而,如果在定义swap、move(或hash、less等)的特化时,应该将该特化放入"namespace std"中。例如:
namespace std { template<> class hash{ public: size_t operator()(const X&) const; } } class X { friend size_t std::hash ::operator()(const X&); };
总结起来,"Using std Namespace"这个问题的出现原因是为了减少代码的冗余度和避免命名冲突。解决方法是在需要使用标准库对象和函数时,可以使用"using std::swap"等语句,但需要注意在头文件中不要使用"using namespace std",并且在定义特化时应该将其放入"namespace std"中。
使用using namespace std;
的问题是它会将所有名称导入到全局命名空间中,并且可能导致各种非明显的歧义。
在std
命名空间中有一些常见的标识符,例如count、sort、find、equal和reverse。如果有一个名为count的局部变量,那么using namespace std
将不能使您使用count而不是std::count。
不希望出现的名称冲突的经典示例如下所示。假设您是初学者,并且不知道std::count。假设您要使用<algorithm>
中的其他内容,或者它被一个看似无关的头文件引入。
#includeusing namespace std; int count = 0; int increment() { return ++count; // error, identifier count is ambiguous }
这个错误通常很长且不友好,因为std::count是带有一些长嵌套类型的模板。
这是可以的,因为std::count进入全局命名空间,函数count将其隐藏。
#includeusing namespace std; int increment() { static int count = 0; return ++count; }
可能有点令人惊讶,但这是可以的。导入到声明范围中的标识符出现在包含定义它们和导入它们的公共命名空间中。换句话说,在全局命名空间中,std::count作为count可见,但仅在increment内部可见。
#includeint increment() { using namespace std; static int count = 0; return ++count; }
由于类似的原因,这里的count是有歧义的。使用using namespace std
不会导致std::count隐藏外部的count,就像人们可能期望的那样。using namespace规则意味着std::count在increment函数中看起来好像是在全局范围内声明的,即与int count = 0;在同一范围内,从而导致歧义。
#includeint count = 0; int increment() { using namespace std; return ++count; // error ambiguous }
但是,没有std::前缀时,编写起来确实容易得多!
:不,不是这样。输入时,五个字符并不重要,但是在阅读时,这五个字符可能非常重要。对于源代码来说,阅读的易读性比输入的容易性更为重要,因为代码的阅读要远远超过代码的编写。
可以补充的是,使用语句遵守作用域规则。
更新内容:添加了一些说明作用域规则的示例。
这可能不是那么糟糕,我真正不喜欢的是简单错误的错误消息变得更加难以解释,或者根本没有发生错误。例如,如果一个函数被认为在作用域内,但实际上不在,而std::函数在作用域内,那么您通常会得到一个更加难以理解的“无法转换参数X”或“无法从模板生成函数”的错误。更糟糕的是,如果错误调用了错误的函数,则会发生静默错误。虽然这种情况很少见,但确实会发生。
我很惊讶没有人讨论过using std::xxx;
的选项。它不会造成命名空间污染,编写代码会更短,而且我认为copy
比std::copy
更易读。