为什么"using namespace std;"被认为是不好的编程实践?

3347

我听说using namespace std;是不好的做法,应该直接使用std::coutstd::cin。这是为什么呢?是否会冒险声明与 std 命名空间中某些东西同名的变量?


719
不要忘记可以使用 "using std::cout;",这意味着您不必每次都输入 std::cout,但同时不要引入整个 std 命名空间。 - Bill
120
在头文件的文件范围内使用“using namespace std”特别不好。在源文件(*.cpp)的文件范围内,在所有包含文件之后使用它并不那么糟糕,因为其影响仅限于单个翻译单位。在函数或类中使用它甚至更少问题,因为其影响仅限于函数或类的作用域。 - sh-
15
我不推荐使用 using 命令,除非针对特定的命名空间,例如 std::literals::chrono_literalsPoco::Data:KeywordsPoco::Units 等与字面值或可读性技巧有关的内容。无论是在头文件还是实现文件中都不应该使用。在函数作用域内可能还可以接受,但除了字面值和相关内容外都没有用处。 - Ludovic Zenohate Lagouardette
22
@Jon:这与特定的命名空间std无关。我强调的重点是“在头文件中的文件作用域”。简言之,不要在头文件的文件作用域中使用“using namespace”(包括std或其他)。在实现文件中使用是可以的。抱歉造成的歧义。 - sh-
16
这只是在头文件中被认为是不好的编程习惯,在未被包含在其他地方的源文件(即cpp文件)中则可以。请参见@mattnewport下面的答案。https://dev59.com/D3M_5IYBdhLWcg3wQQ3w#26722134 - Danra
显示剩余4条评论
42个回答

2831

考虑两个名为Foo和Bar的库:

using namespace foo;
using namespace bar;

一切都正常工作,您可以从Foo调用Blah(),从Bar调用Quux()而没有问题。但有一天,您升级到新版本的Foo 2.0,它现在提供了一个名为Quux()的函数。现在你遇到了冲突:Foo 2.0和Bar都将Quux()引入了全局命名空间中。这需要花费一些精力来解决,特别是如果函数参数恰好相匹配。

如果您使用了foo::Blah()bar::Quux(),那么介绍foo::Quux()就不会产生影响。


586
我一直喜欢Python的"import big_honkin_name as bhn",这样你就可以只用"bhn.something"而不是"big_honkin_name.something",真的减少了输入量。C++有类似的功能吗? - paxdiablo
931
@Pax 命名空间 io = boost::filesystem; 的作用是为了给 boost::filesystem 命名空间起一个别名叫做 io。 - Khaled Alshaya
181
我会尽力进行翻译:我认为把它说成是“一些努力来修复”有些言过其实。因为你不会有新的foo::Quux实例,所以只需用bar::Quux消除当前使用中的歧义。 - MattyT
349
有理智的人会创建一个库,其中包含未经限定的名称与std类型发生冲突吗? - erikkallen
131
标准库包含了大量的名称,其中很多非常流行和常见(例如errorlistsort),因此将其放入自己的命名空间中是一个重要的原因。请注意,这里的“将其放入自己的命名空间中”是指将标准库放入std命名空间中。 - sbi
显示剩余7条评论

1706

Greg写的更糟糕的情况可能会发生!

库Foo 2.0可以引入一个函数Quux(),对于你调用Quux()的一些参数,它是一个明显更好的匹配,而不是你的代码多年来调用的bar::Quux()。然后你的代码仍然编译,但它会静默调用错误的函数,并执行神马操作谁也不知道。这就是最糟糕的事情了。

请记住,std命名空间有大量标识符,其中许多是非常常见的(例如listsortstringiterator等),很可能也出现在其他代码中。

如果你认为这不太可能:在Stack Overflow上曾经有一个问题问到,几乎完全是因为省略了std::前缀而导致调用错误的函数,这发生在我回答这个问题大约半年后。这里是另一个更近期的类似问题的例子。所以这是一个真正的问题。


这里还有一个数据点:很多年前,我也觉得必须在标准库的每个东西前面加上“std::”很烦人。后来,我参与了一个项目,在该项目中决定禁止使用using指令和声明,除了函数范围内。你猜怎么着?我们大多数人只用了很少几周就习惯了写前缀,再过几周,我们大多数人甚至认为它实际上使代码更易读。原因很简单:无论你喜欢短文还是长文都是主观的,但前缀客观地增加了代码的清晰度。不仅编译器,而且你也会发现很容易看出引用了哪个标识符。
十年后,该项目已经有数百万行代码。由于这些讨论一次又一次地出现,我曾经好奇(允许的)函数作用域using实际上被在项目中使用了多少次。我在源代码中搜索并只找到了一两十个使用它的地方。对我来说,这表明,一旦尝试过,即使在允许使用的情况下,开发人员也不觉得std::很痛苦,甚至在每100 kLoC中也不会使用一次。
底线:明确地给所有东西加前缀不会造成任何伤害,需要很少的适应时间,并且有客观的优势。特别是,它使编译器和人类读者更容易解释代码 - 这应该是编写代码时的主要目标。

1
读者对 foo::bar() 的解释意见不一,它可能表示命名空间 foo 中的函数 bar,也可能表示类 foo 的静态函数。 - convert
6
为什么有人会将一个类命名为 foo 而不是 Foo?静态方法也应该被称为 Foo::Bar 而不是 Foo::bar。这就是为什么人们认为规范是一件好事的原因。 - Stefan Riedel
1
@Stefan Riedel 不知道,但在C++中这是常见做法。例如,使用string和vector而不是String和Vector。 - convert
5
在标准库中,这是常见的做法。大多数(我所知道的全部)C++编码规范都建议对类名使用大写字母。超过一半的规范建议对静态方法使用大写字母。即使你有一些奇怪的编程约定不这样做,将 foo::bar 作为静态方法仍然不能反驳解释观点。它仍然更清晰地表明该函数/方法属于哪个类,如果你给你的类起一个好的名称,仍然很清楚表示这是一个类而不是命名空间。 - Stefan Riedel
12
“停止玩亡灵法师”。这不是一个聊天盒子,也不是一个组织节日的论坛,其中日历时间本身并不重要。这是一个“知识库”,在这里仅有日期是无关紧要的,而像“相关性”和“一致性”这样的东西才是重要的。这个话题(问题)具备这两个方面的条件,同样也包括了答案。所以,“我们应该停止”误解SO的真正意义。(注意:您实际上可以被奖励去更新一个旧的项目,以有用的方式完成翻译。) - Sz.
显示剩余4条评论

574
在类的头文件中放置using namespace的问题在于,它会强制任何想要使用你的类(通过包含你的头文件)的人也“使用”(即查看所有内容)那些其他命名空间。
但是,你可以在你的(私有的)*.cpp文件中放置using语句。
请注意,有些人不同意我这样说“随意”——因为尽管在cpp文件中使用using语句比在头文件中使用更好(因为它不会影响包含您的头文件的人),但他们认为它仍然不是很好(因为根据代码的情况,它可能会使类的实现更难以维护)。这篇C ++超级常见问题解答说,

using指令存在于遗留的C++代码和简化过渡到命名空间,但你可能不应该在正常情况下使用它,至少不在你的新C++代码中。

常见问题解答建议两个替代方案:

  • A using-declaration:

    using std::cout; // a using-declaration lets you use cout without qualification
    cout << "Values:";
    
  • Just typing std::

    std::cout << "Values:";
    

9
当然,你也不应该假定全局输出流的状态,以防万一有人使用了std:cout << std::hex,但是没有在后面使用std::restore_cout_state 进行恢复。但这是另一个完全不同的问题。 - Móż
虽然关于头文件的担忧是可以理解的,因为包含文件可能会产生副作用,但我认为在cpp文件的情况下并不是这样。让我们看看在几乎所有其他编程语言中都会发生什么。例如,在Java中编码时,您几乎总是从使用的包中导入每个符号 - 特别是标准符号。这意味着您几乎永远不会期望有竞争和冲突的String、List、Map等实现。我知道的其他语言也是如此。在我看来,这是合理的,我们应该让生活变得简单而不是困难。 - Dimitrios Menounos
2
对于大多数C++生态系统,包括C++委员会、经验丰富的C++开发人员和C++语言的创造者来说,这不仅是一种选择,而且也是推荐的选择。 - spectras
如果C和C++包含指令,基本上是说“在此之前标记存在的标识符”和“忘记在上一个标记后定义的所有标识符并增加静态作用域计数”,那么这两个指令将语义上将程序的细分分为文件和翻译单元,允许统一构建具有与单独模块编译相同的语义。 - supercat
@supercat 你是想标记我吗?我是在回应Kiruahxh的留言,他说对于他来说命名空间不是一个选项。我不确定如何在那种情况下解释你的信息。 - spectras
显示剩余5条评论

273

最近我遇到了一项有关Visual Studio 2010的投诉。结果发现几乎所有源文件都包含以下两行:

using namespace std;
using namespace boost;

许多Boost特性正在被纳入C++0x标准,而Visual Studio 2010有很多C++0x特性,因此这些程序突然无法编译。

因此,避免使用using namespace X;是一种未来保护的形式,一种确保所使用的库和/或头文件的更改不会破坏程序的方法。


31
这个。Boost和std有很多重叠 - 特别是自从C++11以来。 - einpoklum
2
我曾经这样做过,但是通过吃了亏的方式学到了教训。现在我从不在函数定义之外使用 using,而且很少使用 using namespace - Ferruccio
我个人永远不会使用boost,因为它是我见过的最糟糕的C++ API。如果使用namespace std,我还可能遇到哪些问题? - convert
任何库理论上都可能与std现在或将来发生冲突。如其他答案所述,std包含许多常见名称,如list和error。Boost只是突出了它现在受到的影响。使用using撤消了命名空间本应修复的问题。请小心使用。 - wheredidthatnamecomefrom

238

简短版:在头文件中不要使用全局的using声明或指令,但在实现文件中可以自由使用。以下是Herb SutterAndrei Alexandrescu在《C++编程规范》中对这个问题的看法(加粗部分是我强调的):

总结

命名空间 using 为你方便而生,不应该被你强加给别人:永远不要在 #include 指令前写 using 声明或指令。

推论:在头文件中,不要写命名空间级别的 using 指令或声明;而是显式地命名空间限定所有名称。(第二条规则是从第一条得出的,因为头文件永远不知道可能在它们之后出现的其他头文件 #include)。

讨论

简而言之:在 #include 指令后你可以自由使用命名空间 using 声明和指令,并且感觉良好。尽管有反复的说法,命名空间 using 声明和指令不是邪恶的,并且它们并没有破坏命名空间的目的。相反,它们使命名空间可用


15
这里只是另一个程序员的意见,虽然我完全同意在头文件中永远不应该出现单词“using”,但我不太确定在代码中任何位置都可以加上“using namespace xyz;”,特别是如果“xyz”是“std”的话。我使用“using std::vector;”的形式,因为这只会将命名空间中的一个元素引入伪全局作用域,从而减少了冲突的风险。 - dgnuff
6
我不禁感觉 using namespace 就像 goto 一样邪恶。两者都有其合理的用途,但在 1000 次中会有 999 次被错误使用。是的,在源代码中使用 using namespace 不会污染其他引用的命名空间,很整洁。但是它仍然不能保护你免受 using namespace Foo + using namespace Bar 带来的麻烦,例如你调用(隐式 Foo::)baz(xyz),突然之间代码崩溃了(没有任何相关更改),因为 Bar::baz() 在别处添加了,而它刚好匹配得更好(因此现在被调用)。 - CharonX
1
@CharonX 但是如果调用Foo :: baz()的源文件实际上#include声明了Bar :: baz()的头文件,那么代码不会崩溃吗?这似乎不太可能发生。这就像我在main.cpp文件中编写using namespace std;,但没有#include <iostream>,然后我仍然可以在main.cpp中定义一个名为cout的函数,并且不会发生冲突。 - AdmiralAdama
3
@AdmiralAdama 当然,需要包含该头文件 - 但这可以通过间接方式完成(头文件包含其他头文件等)。因此,这种错误比较罕见...但是它发生时,它可能非常严重(您调用的函数会更改),非常难以检测(通过在某个地方添加函数来触发,因此它进入发布的风险很高)并且难以追踪(代码“看起来”100%正确)。我在软件工程中的答案中给出了更详细的示例。 - CharonX
@AdmiralAdama:这是错误的,系统头文件允许包含其他系统头文件,因此即使您没有#include <iostream>std::cout也可能在作用域内,如果您现在编写using namespace std;,则您的代码在某些平台上运行并在其他平台上崩溃,具体取决于一个系统头文件是否包含另一个系统头文件的细节(请注意,只需要一个头文件#include <iosfwd>,这个头文件几乎是为了从其他头文件中包含而存在的)。 - Ben Voigt

144

在全局范围,特别是头文件中,不应该使用 using 指令。然而,在某些情况下,在头文件中使用它是合适的:

template <typename FloatType> inline
FloatType compute_something(FloatType x)
{
    using namespace std; // No problem since scope is limited
    return exp(x) * (sin(x) - cos(x * 2) + sin(x * 3) - cos(x * 4));
}

这比显式限定符(std::sinstd::cos……)更好,因为它更短,并且具有通过参数相关的查找(ADL)与用户定义的浮点类型一起使用的能力。


4
@Billy:没有其他方法来支持调用userlib::cos(userlib::superint)。每个功能都有其用途。 - Zan Lynx
20
当然可以。using std :: cos;using std :: sin等等。但问题在于,任何设计良好的userlib都会将他们自己的sinecosine放在自己的命名空间中,因此这并没有真正帮到你。(除非在此模板之前有一个using namespace userlib,但是这样做就跟using namespace std一样糟糕--而且那里的作用域是不受限制的)。此外,我只看到类似这样的函数出现在swap中,并且在这种情况下,我建议只需创建std:: swap的模板特化版本,避免整个问题。 - Billy ONeal
13
template<typename T> void swap(MyContainer<T>&, MyContainer<T>&) 意为“交换两个 MyContainer<T> 类型的对象”,由于缺乏函数模板偏特化,有时需要使用重载来实现。 - sbi
43
你的(被点赞了7次!)评论是错误的——你描述的情况正是ADL设计的覆盖范围。简单来说,如果x有一个或多个“关联命名空间”(例如,如果它定义在namespace userlib中),那么任何看起来像cos(x)的函数调用都会额外查找这些命名空间——而不需要先使用using namespace userlib;。Zan Lynx是正确的(而C++名称查找非常复杂…)。 - j_random_hacker
5
我更喜欢使用 using std::sin; using std::cos; using std::exp;,而不是 using namespace std;。这样可以获得相同的好处,而避免了将 std::* 放入函数中所带来的风险。 - Ferruccio
显示剩余2条评论

114

不要在全局范围内使用

只有在全局使用时才被认为是“不好的”。因为:

  • 你会弄乱你正在编程的命名空间。
  • 当你使用许多using namespace xyz;时,读者将难以看出特定标识符来自哪里。
  • 对于源代码的其他读者来说是真实存在的事情,但最频繁的读者——你自己——甚至更加如此。过一两年再回来看看……
  • 如果你只谈论using namespace std;,你可能没有意识到你获取了所有的内容——当你添加另一个#include或切换到新的C++版本时,你可能会遇到你不知道的名称冲突。

可以在本地使用

可以放心地在本地使用(几乎)自由。当然,这样可以避免std::的重复使用——而重复使用也是不好的。

使用它的惯用语

C++03中,有一个惯用语——样板代码——用于实现类的swap函数。建议你实际上使用一个本地的using namespace std;——或至少使用using std::swap;

class Thing {
    int    value_;
    Child  child_;
public:
    // ...
    friend void swap(Thing &a, Thing &b);
};
void swap(Thing &a, Thing &b) {
    using namespace std;      // make `std::swap` available
    // swap all members
    swap(a.value_, b.value_); // `std::stwap(int, int)`
    swap(a.child_, b.child_); // `swap(Child&,Child&)` or `std::swap(...)`
}

这里的魔法操作如下:

  • 编译器将选择value_std::swap,即void std::swap(int, int)
  • 如果您有一个实现了void swap(Child&, Child&)的重载,则编译器将选择它。
  • 如果您没有该重载,则编译器将使用void std::swap(Child&, Child&)并尽力交换它们。

使用C++11后,不再需要使用此模式。 std::swap的实现已更改为查找潜在的重载并选择它。


7
“std::swap”的实现已更改为查找潜在的重载并选择它。” - 什么?你确定吗?虽然在C++11中提供自定义的swap不再那么重要,因为std::swap本身更灵活(使用移动语义),但是std::swap自动选择您自己的自定义交换,这对我来说是绝对新的事情(我真的不相信)。 - Christian Rau
19
即使在使用swap的情况下,更清晰(而且幸运的是更常见)的习惯用法是写成using std::swap;而不是using namespace std;。这种更具体的习惯用法副作用较少,因此使代码更易于维护。 - Adrian McCarthy
@AdrianMcCarthy 我明白 using std::swap 惯用法即使涉及非惯用代码也是可用的。但我同意,当您对源代码有100%的控制权时,最好具体说明。 - towi
16
最后一句话是错误的。在C++11中,Std Swap Two Step 被正式认可为调用 swap 的正确方式,并且标准中的其他各个地方也被更改为以那种方式调用 swap(请注意,如上所述,使用using std::swap 是正确的方式,而不是 using namespace std)。但是,std::swap 本身明确更改以查找其他 swap 并使用它。如果调用了 std::swap,则将使用 std::swap - Jonathan Wakely
6
为了减少本地命名空间并创建自说明的代码,局部使用 using std::swap 可能更明智。你很少对整个 std 命名空间感兴趣,所以只需挑选你感兴趣的部分即可。 - Lundin
显示剩余2条评论

88

如果你引入了正确的头文件,你会突然发现像 hexleftplus 或者 count 这样的名称出现在了全局范围内。如果你不知道这些名称在 std:: 中,这可能会让你惊讶。如果你还试图在本地使用这些名称,可能会导致相当大的混淆。

如果所有标准库的内容都在它自己的命名空间中,你就不必担心与你的代码或其他库产生命名冲突。


15
+1 不用说“距离”这个问题。但只要实际可行,我更喜欢非限定名称,因为这样可以提高可读性。而且,我们通常在口语中不会加上限定词,愿意花时间澄清可能的歧义,这意味着能够理解没有限定词的谈话是有价值的。应用到源代码中,这意味着它的结构清晰明了,即使没有限定词也能清楚地知道它所指的内容。 - Cheers and hth. - Alf
公平地说,如果不包括<iomanip>,你就没有大多数这些内容。不过,说得好。 - einpoklum
1
通常情况下,您不需要包含 <iomanip> 来获取那些内容。在 GCC 中,包含 <iostream> 即可满足所有这些需求,例如 https://gcc.godbolt.org/z/Kqx9q1 - Ayxan Haqverdili
相信您只需要使用<iomanip>来处理需要参数的操作符,例如setw - celticminstrel
我的个人观点是:任何与std发生命名冲突的问题都应该被视为bug,并且在发现后应该尽快得到解决。 - user2793784

65
另一个原因是出乎意料。

如果我看到 cout << blah,而不是 std::cout << blah,我会想:这个 cout 是什么?它是普通的 cout 吗?还是特殊的东西?

43
这是一个玩笑吗?我真的无法辨别。如果不是,那么我个人会认为这是正常的“cout”,除非您不信任代码,否则这将是一个超级大的代码坏味道,在我看来。... 如果您不信任代码,那么为什么首先要使用它?请注意,我并不是说“相信所有东西!”,但是如果你正在处理一些来自GitHub等知名库的代码,这也似乎有点牵强。 - Brent Rittenhouse
55
“cout”这个例子不好,因为每个人都能认出它。但是想象一下在一个金融应用程序中的“future”。它是一个在指定日期买入或卖出某物品的合同吗?不是的。如果代码使用了“std::future”,你就不会那么容易混淆了。 - James Hollis
2
@BrentRittenhouse 可能是一个不太好的例子,至少有四个不同的库有cout。可能是“它是标准库吗?libstdc++?stl?还是其他什么?”并且,并非每个人都知道std::cout,至少本质上不知道,我们接收的7个新员工中有6个不知道。因为教育课程没有使用这些内容。我必须驱逐printf。或者从Qt调试()。 - Swift - Friday Pie
3
真的吗?在很多C++书籍的第一章的第一个例子中,它(使用插入运算符)是一些新手所知道的唯一的C++知识。请问需要翻译什么其他内容吗? - mckenzm
@mckenzm 我可能会把它放在书籍或讲义中以减少混乱,但不会在代码中。 - Martin Beckett
@Swift-FridayPie 即使我接受的教育机构仍然以C和C++为起点...我们并没有花太多时间在printf上。Cout非常早就被引入了,而且基本上是我们被允许使用的唯一的C++东西,因为我们实现了很多其他限制在C++粗略子集中的东西。从第一天开始,我们可以使用printf或cout,而大多数人选择了cout,因为语法更容易记忆。当然,在求和时我们不能使用std::accumulate,但printf在算法上并不重要。 - ttbek

53

我认为它不应该在全局范围内使用,但是在本地使用并不那么恶劣,比如在namespace中。这里有一个来自《C++程序设计语言》的例子:

namespace My_lib {

    using namespace His_lib; // Everything from His_lib
    using namespace Her_lib; // Everything from Her_lib

    using His_lib::String; // Resolve potential clash in favor of His_lib
    using Her_lib::Vector; // Resolve potential clash in favor of Her_lib

}
在这个例子中,我们解决了由于命名空间组合而产生的潜在名称冲突和歧义问题。显式声明的名称(包括使用 using-declarations 声明的名称,例如 His_lib::String)优先于使用 using-directive(如 using namespace Her_lib)在另一个作用域中访问的名称。

有趣的是,大多数其他答案忘记通过使用花括号 {..} 来定义命名空间的范围。 - Ol Sen

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