使用std命名空间

124

在使用与std命名空间相关的“using”时,似乎存在不同的观点。

有人说要使用“using namespace std”,而另一些人则说不要这样做,而是要在使用的std函数前加上“std::”前缀,还有一些人建议使用类似于以下内容:

using std::string;
using std::cout;
using std::cin;
using std::endl;
using std::vector;

对于所有要使用的标准函数,它们各自有哪些优缺点?


为什么“using namespace std”被认为是不良实践? - Vadzim
如何解决C++命名空间和全局函数之间的名称冲突?(https://dev59.com/XG865IYBdhLWcg3wUs_z) - Vadzim
请重新检查答案,给任何“有帮助”的答案点赞,并接受对你有效的答案。如果你的原始问题没有得到解答,请在答案下留言。谢谢。 - Simon Sobisch
16个回答

153
大多数C++用户都很喜欢使用std::string, std::vector等标准库容器。事实上,看到一个裸露的vector让我想知道这是std::vector还是不同的用户定义vector
我一直反对使用using namespace std;。它会将各种名称导入全局命名空间,并可能导致各种不明显的歧义。
以下是一些常见的在std命名空间中的标识符:count、sort、find、equal、reverse。如果有一个叫做count的局部变量,那么使用using namespace std就无法使用count代替std::count
一个不想要的名称冲突的典型例子是像下面这样的情况。想象一下,您是初学者,不知道std::count。想象一下,您正在使用<algorithm>中的其他东西,或者它已经被一个看似不相关的头文件引入。
#include <algorithm>
using namespace std;

int count = 0;

int increment()
{
    return ++count; // error, identifier count is ambiguous
}

错误通常很长且不友好,因为std::count是一个带有一些嵌套类型的模板。

尽管如此,这没关系,因为std::count进入全局命名空间,并且函数count将其隐藏起来。

#include <algorithm>
using namespace std;

int increment()
{
    static int count = 0;
    return ++count;
}
或许有些令人惊讶,但这是可以的。导入到声明作用域中的标识符会出现在公共命名空间中,该命名空间包含定义它们和导入它们的位置。换句话说,std::count 在全局命名空间中可见,但仅在 increment 内部可见作为 count
#include <algorithm>

int increment()
{
    using namespace std;
    static int count = 0;
    return ++count;
}

出于类似的原因,count 在此处是有歧义的。使用 using namespace std 并不会将 std::count 隐藏起来,像预期的那样隐藏外部的 countusing namespace 规则意味着在 increment 函数中,std::count 看起来就像是在全局作用域声明的,也就是和 int count = 0; 相同的作用域位置,从而导致了歧义。

#include <algorithm>

int count = 0;

int increment()
{
    using namespace std;
    return ++count; // error ambiguous
}

25
没有std::前缀打起来就_超级_容易! - xtofl
80
不,它并不是这样。在输入时,五个字符并不那么重要,但是当阅读时,这五个字符可能非常重要。对于源代码来说,易读性比打字容易程度更加重要,因为代码被阅读的次数远多于被编写的次数。 - sbi
3
你可以补充说,using语句在作用域规则方面表现正确。 - Martin York
2
值得注意的是,如果您有以下代码:int swap; using namespace std; int main() { ::swap = 1; },它始终是非歧义的:显式指定作用域将搜索使用指令放置的命名空间 - 但它永远不会与现有声明发生冲突。这只会在未经限定的名称查找时发生。 - Johannes Schaub - litb
6
我很惊讶没有人讨论“使用std::xxx”的选项。这不会污染命名空间,编写代码会更短,我认为“copy”比“std::copy”更易读。 - legends2k
显示剩余7条评论

45

除了基础知识(需要在所有STL对象/函数前面添加std::并且如果不使用'using namespace std'则冲突的可能性较小)

还值得注意的是,您永远不应该放置

using namespace std

在头文件中使用命名空间可能会传播到包含该头文件的所有文件,即使它们不想使用该命名空间也是如此。

在某些情况下,使用诸如

using std::swap

如果有一个专用版本的swap,编译器将使用该版本,否则它将回退到std::swap

如果调用std::swap,你总是使用基本版本,它不会调用优化版本(如果存在)。


1
+1 表示“u n s”可以传播。请注意,它也可以潜入正确构建的标头中:只需在流氓标头之后包含即可。 - quamrana
1
但是,如果您正在定义swapmove(或hashless等)专业化,则应将该专业化放入namespace std中。例如:namespace std {template<> class hash<X> {public: size_t operator()(const X&) const};} class X: {friend size_t std::hash<X>::operator()(const X&)}; - AJMansfield
@AJMansfield,将swap重载放入std命名空间是不推荐的,请参考https://stackoverflow.com/questions/14402990/should-you-overload-swap-in-the-std-namespace。 - undefined

28

首先,一些术语:

  • using-declaration: using std::vector;(使用声明)
  • using-directive: using namespace std;(使用指令)

我认为使用使用指令是可以的,只要它们不在头文件的全局范围内使用。因此,有

using namespace std;

在您的 .cpp 文件中使用 std 命名空间并没有什么问题,如果需要,完全可以在您的控制下进行操作(如果需要,甚至可以限定到特定块)。我认为没必要用一堆 std:: 限定符来堆砌代码 - 这只会变成一堆可视化噪声。然而,如果您的代码中没有使用很多 std 命名空间的名称,那么也没问题,可以略去指示符。这是一个重言 - 如果不需要指示符,则无需使用它。
同样地,如果您可以通过几个 using-declarations(而不是 using-directives)来引入 std 命名空间中的特定类型,则没有理由不将这些特定名称带入当前命名空间。同理,如果存在 25 或 30 个 using-declarations,那么只需要一个 using-directive 即可解决,因此,采用一种只使用 using-directive 的方式会更加简洁便利。
同时,需要牢记的是有时候您必须使用 using-declaration。请参考 Scott Meyers 在 Effective C++,第三版中的“条款 25:考虑支持非抛出交换”一章。为了使通用的模板函数能够使用参数化类型的最佳 swap 方法,您需要使用 using-declaration 和 ADL 或 Koenig 查找(argument dependant lookup)。
template< typename T >
void foo( T& x, T& y)
{
    using std::swap;     // makes std::swap available in this function

    // do stuff...

    swap( x, y);         // will use a T-specific swap() if it exists,
                         //  otherwise will use std::swap<T>()

    // ...
 }

我认为我们应该看一下各种语言中普遍使用命名空间的常见习语。例如,Java和C#在很大程度上使用命名空间(可以说比C++更多)。这些语言中命名空间中的名称最常见的用法是通过相当于using-directive的方式将它们一次性带入当前范围。这不会引起广泛的问题,而少数情况下出现问题时,则通过完全限定的名称或别名处理有关名称来处理异常,就像在C++中可以做的那样。
Herb Sutter和Andrei Alexandrescu在他们的书"C++ Coding Standards: 101 Rules,Guidelines, and Best Practices"的"项目59: 不要在头文件或#include之前编写namespace usings"中这样说:
简而言之:您可以并且应该在您的实现文件中自由地使用namespace using声明和指令,而在#include指令之后感到满意。尽管一再声称相反,但namespace using声明和指令并不是邪恶的,也不会破坏命名空间的目的。相反,它们是使命名空间可用的东西。
Stroupstrup在《C++ Programming Language, Third Edition》中经常引用“不要污染全局命名空间”。他确实说过这句话(C.14[15]),但是他提到了第C.10.1章,在那里他说:

A using-declaration adds a name to a local scope. A using-directive does not; it simply renders names accessible in the scope in which they were declared. For example:

namespaceX {
    int i , j , k ;
}

int k ;
void f1()
{
    int i = 0 ;

    using namespaceX ; // make names from X accessible

    i++; // local i
    j++; // X::j
    k++; // error: X::k or global k ?

    ::k ++; // the global k

    X::k ++; // X’s k
}

void f2()
{
    int i = 0 ;

    using X::i ; // error: i declared twice in f2()
    using X::j ;
    using X::k ; // hides global k

    i++;
    j++; // X::j
    k++; // X::k
}

A locally declared name (declared either by an ordinary declaration or by a using-declaration) hides nonlocal declarations of the same name, and any illegal overloadings of the name are detected at the point of declaration.

Note the ambiguity error for k++ in f1(). Global names are not given preference over names from namespaces made accessible in the global scope. This provides significant protection against accidental name clashes, and – importantly – ensures that there are no advantages to be gained from polluting the global namespace.

When libraries declaring many names are made accessible through using-directives, it is a significant advantage that clashes of unused names are not considered errors.

...

I hope to see a radical decrease in the use of global names in new programs using namespaces compared to traditional C and C++ programs. The rules for namespaces were specifically crafted to give no advantages to a ‘‘lazy’’ user of global names over someone who takes care not to pollute the global scope.

通过使用 using-directive,您可以像“懒惰地使用全局名称”的用户一样获得相同的优势,并且可以安全地将命名空间中的名称提供给当前范围。请注意,有一个区别 - 通过正确使用 using-directive(将指令放置在 #includes 后面),使得 std 命名空间中的名称可用于作用域时,不会污染全局命名空间。它仅仅是方便地使这些名称可用,并继续保护免受冲突的影响。

但是我看到的Java和C#程序通常会引入它们使用的所有命名空间 - 不仅仅是 "System"(或其等价物)。因此,不是一个单一的using指令带来所有使用的名称,而是有5个或10个指令做更多或更少相同的事情。此外,“using namespace std;”真的会引起那么多麻烦吗? - Michael Burr
1
我仍然不相信这是一个真实世界中的问题。我经常看到使用指令而没有遇到严重的缺点。但是,如果不使用它们,我也不会有任何问题。我只是希望 std:: 限定符不会混杂在代码中 — 还有其他方法来避免这种情况(通常使用声明或类型定义就可以了)。 - Michael Burr
@Michael,你能相信我花了时间删除using-declarations以避免冲突吗?在Lisp解释器中,list是用于识别列表的自然标识符。如果你使用的命名约定自然地避免与标准库发生冲突(例如类型名称以大写字母开头...),那么你不会看到问题是很正常的。你很幸运,C++0X已经放弃了这些概念,所以你不会很快遇到这样的名称... - AProgrammer
1
@AProgrammer:你说,“list是Lisp解释器中识别列表的自然标识符” - 但是有“using namespace std;”指令并不会阻止您声明自己的自然标识符'list' - 只是如果您这样做,就不能再使用未经限定的std::list。 这与没有“using namespace std;”指令没有什么区别。或者我错过了什么? - Michael Burr
@Michael,我修改了我的回答并解释了发生了什么。这个故事太长了,不适合在评论中讲述。 - AProgrammer
显示剩余3条评论

17
不要在头文件的全局作用域中使用 using namespace。这可能会导致冲突,而出现冲突的文件的负责人对原因没有控制权。
在实现文件中,选择要少得多。
- 使用 using namespace std 会引入该命名空间中的所有符号。这可能会带来麻烦,因为几乎没有人知道其中所有的符号(因此在实践中无法应用无冲突的策略),更不用说将要添加的符号了。C++标准允许一个头文件添加其他头文件的符号(C语言则不允许)。在受控情况下简化编写仍然可以很好地工作。如果出现错误,则在出现问题的文件中检测到该错误。 - 使用 using std::name; 具有编写简单且不会导入未知符号的优点。成本是您必须显式导入所有想要的符号。 - 显式限定名称有点混乱,但我认为这是最不麻烦的做法。
在我的项目中,我对所有名称都使用显式限定,接受使用 std::name,并反对使用 namespace std(我们有一个具有自己列表类型的 lisp 解释器,所以冲突是肯定的)。
对于其他命名空间,您还必须考虑使用的命名约定。我知道有一个项目在名称中使用 namespace(用于版本控制)和前缀。这样做一个 using namespace X 几乎没有风险,不这样做会导致代码看起来很愚蠢。
有些情况下您需要导入符号。std::swap 是最常见的情况:您导入 std::swap,然后使用未限定的 swap。如果类型的命名空间中存在适当的 swap,则参数相关查找将找到该 swap,否则将退回到标准模板。
编辑:
在评论中,Michael Burr 想知道现实生活中是否会发生冲突。这里有一个真实的例子。我们有一个扩展语言,是 Lisp 方言。我们的解释器有一个包含 lisp.h 的头文件。
typedef struct list {} list;

我们需要集成和调整一些代码(我将其称为“引擎”),它看起来像这样:

#include <list>
...
using std::list;
...
void foo(list const&) {}

所以我们进行了如下修改:

#include <list>

#include "module.h"
...
using std::list;
...
void foo(list const&) {}

很好,一切正常。几个月后,“module.h”被修改以包括“list.h”。测试通过了。“module”没有以影响其ABI的方式进行修改,因此“engine”库可以在不重新编译其用户的情况下使用。集成测试也是OK的。新的“module”发布了。当引擎的代码没有被修改时,下一次编译就会失败。


1
我认为使用命名空间的一个受控情况是在发布代码时,这种简化有助于页面布局并帮助集中注意力。缺点是它并不能真正展示良好的实践,因此我不会在初学者的书籍中使用它。 - AProgrammer
1
我认为打std::是为了清晰而付出的小代价。 - paoloricardo
4
另一方面,我认为到处都出现std::是不必要的视觉杂乱。 - Michael Burr
1
@Michael:你出钱,你做决定! - paoloricardo
2
感谢您抽出时间添加遇到的问题细节。 - Michael Burr

4
如果您的代码中不存在与std和其他库的名称冲突的风险,您可以使用以下内容:
using namespace std;

但如果您想准确了解文档中代码的依赖关系,或者存在名称冲突的风险,请使用另一种方式:

using std::string;
using std::cout;

第三种解决方案是,在代码中不使用这些解决方案,而在每个使用前写上std::,这样可以提高安全性,但可能会让代码变得稍微繁琐一些...

4

两者都

using std::string;

并且

using namespace std;

在全局命名空间中添加一些符号(一个或多个)。在头文件中添加符号到全局命名空间是不应该做的事情。你无法控制谁会包含你的头文件,有很多头文件包含其他头文件(和包含其他头文件的头文件等等...)。

在实现(.cpp)文件中,这取决于你(只需记住在所有#include指令之后执行)。你只能破坏这个特定文件中的代码,所以更容易管理和找出名称冲突的原因。如果你喜欢在标识符之前使用std::(或任何其他前缀,在你的项目中可能有许多命名空间),那么没问题。如果你想要将你使用的标识符添加到全局命名空间中,也可以。如果你想把整个命名空间都带在头上 :-),那就由你了。虽然影响仅限于单个编译单元,但是它是可以接受的。


4

对我来说,我更喜欢在可能的情况下使用::

std::list<int> iList;

我讨厌写作:


for(std::list<int>::iterator i = iList.begin(); i != iList.end(); i++)
{
    //
}

希望有了C++0x,我可以这样写:
for(auto i = iList.begin(); i != iList.end(); i++)
{
    //
}

如果命名空间非常长,
namespace dir = boost::filesystem;

dir::directory_iterator file("e:/boost");
dir::directory_iterator end;

for( ; file != end; file++)
{
    if(dir::is_directory(*file))
        std::cout << *file << std::endl;
}

@AraK: namespace dir = boost::filesystem; 我猜这是一个别名? - paoloricardo
@paoloricardo:是的,就是这样。 - sbi
2
иҝӯд»ЈеҷЁеә”иҜҘдҪҝз”Ё++iиҖҢдёҚжҳҜi++жқҘйҖ’еўһпјҢеӣ дёәеҰӮжһңе®ғиў«е®ҡд№үпјҢдјҡеҲӣе»әдёҖдёӘдёҚеҝ…иҰҒзҡ„дёҙж—¶еүҜжң¬гҖӮ - Felix Dombek

2
命名空间用于将代码包含在内,防止函数签名的混淆和污染。
这里是适当使用命名空间的完整文档演示。
#include <iostream>
#include <cmath>  // Uses ::log, which would be the log() here if it were not in a namespace, see https://dev59.com/Kmct5IYBdhLWcg3wsPd5

// Silently overrides std::log
//double log(double d) { return 420; }

namespace uniquename {
    using namespace std;  // So we don't have to waste space on std:: when not needed.

    double log(double d) {
        return 42;
    }

    int main() {
        cout << "Our log: " << log(4.2) << endl;
        cout << "Standard log: " << std::log(4.2);
        return 0;
    }
}

// Global wrapper for our contained code.
int main() {
    return uniquename::main();
}

输出:

Our log: 42
Standard log: 1.43508

2
在头文件的命名空间范围内,您永远不应该使用using namespace std。此外,我认为大多数程序员看到没有std::vectorstring时会感到困惑,因此我认为最好不要使用using namespace std。因此,我主张完全不使用using namespace std
如果您觉得必须使用,请添加本地using声明,例如using std::vector。但是请问自己:这值得吗?一行代码只写一次(也许两次),但是它被阅读了十次、百次甚至千次。与阅读代码的努力相比,添加using声明或指令所节省的输入工作微不足道。
考虑到这一点,十年前我们决定明确限定所有标识符的完整命名空间名称。起初似乎很笨拙,但在两周内就变得很常规。现在,在该公司的所有项目中,没有人再使用using指令或声明(有一个例外,见下文)。看着代码(几个MLoC)十年后,我觉得我们做出了正确的决定。
我发现通常那些反对禁止using的人通常还没有尝试过用于一个项目。而那些尝试过的人往往在很短的时间内发现它比using指令/声明更好。
注意:唯一的例外是using std::swap,这是必要的(特别是在通用代码中),以选择无法放入std命名空间的swap()重载(因为我们不允许将std函数的重载放入此命名空间)。

3
std::swap 的一个特化将会是一个完全特化 - 无法部分特化函数模板。只要特化依赖于用户定义类型,任何程序都可以对任何标准库模板进行部分特化。 - CB Bailey
2
我认为using namespace指令的意图并不是为了使打字更容易;相反,它是为了使阅读更容易,因为正如你所说,那段代码将被阅读数十次、数百次甚至数千次。对于一些人来说,少一些std::的干扰会让代码更容易阅读。但这可能取决于个人的感知能力;有些人会过滤掉std::或者需要它作为指引(就像衬线字体),而另一些人则会在其上跌跌撞撞,感觉像在崎岖的道路上行驶。 - Lumi
1
@sbi:不,这并不客观。这取决于您是否认为std::有帮助或者是多余的。更多的多余 -> 更少的清晰度。 - Joshua Richardson
@Joshua: 我们能否达成共识,编译器是客观的呢? - sbi
@sbi: 可能吧。;-) 不同的编译器会有不同的处理方式。有时候是正确的,有时候是错误的,有时候更好,有时候更差(主观上)。很难做出普遍性的陈述,这没关系,因为我不喜欢被告知我“必须”如何做某事,除非它真的无可否认地更好。 - Joshua Richardson
显示剩余5条评论

0

using namespace std 导入了当前命名空间中的 std 命名空间的内容。因此,优点是您不必在该命名空间的所有函数前面键入 std::。但是,可能会出现您有不同的命名空间具有相同名称的函数的情况。因此,您可能最终没有调用您想要的那个。

手动指定要导入到 std 中的函数可以防止发生这种情况,但可能会导致文件开头出现很长的使用列表,这可能会让一些开发人员感到丑陋 ;)!

个人而言,我更喜欢每次使用函数时都指定命名空间,除非命名空间太长,在这种情况下,我会在文件开头放置一些使用语句。

编辑:如另一个答案中所述,您永远不应在头文件中放置 using namespace,因为它将传播到包括此头文件的所有文件中,从而可能产生不希望的行为。

编辑2:根据 Charles 的评论纠正了我的答案。


2
using namespace std;std 命名空间的内容导入全局命名空间。它不会改变默认命名空间。在 using namespace std 之后在全局命名空间中定义某些内容并不能自动将其放入 std 命名空间中。 - CB Bailey
抱歉,这不是我想表达的意思。感谢您指出,我会更正我的答案。 - Wookai
1
大家好:感谢回复。总的来说,不使用“using namespace std”并避免创建潜在的歧义更为安全。总体而言,我更喜欢使用“std::xxx”而非在源文件开头声明各种函数列表,因为这样可以明确无误地说明自己的意图。 - paoloricardo
1
请引用(除非命名空间太长)。您可以使用命名空间别名来帮助。'namespace Rv1 = Thor::XML::XPath::Rules::Light::Version1;' 注意,别名和使用都遵守作用域规则; - Martin York

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接