为什么"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个回答

52

有经验的程序员使用解决他们问题的方法,避免产生新问题,并且出于这个原因,他们会避免在头文件级别使用using-directives。

有经验的程序员也会尽量避免在源文件中完全限定名称。其中一个小原因是,在少量代码就足够时写更多的代码并不优雅 除非有好的原因。一个主要原因是关闭参数相关查找(ADL)。

那么这些 好的原因 是什么呢?有时候程序员明确想要关闭 ADL,其他时候他们想要消除歧义。

因此以下情况是可以接受的:

  1. 函数实现内部的函数级 using-directives 和 using-declarations
  2. 源文件级 using-declarations
  3. (有时候) 源文件级 using-directives

35

看到代码并知道它是干什么的感觉真好。如果我看到std::cout,我知道那是std库的cout流。但如果我看到cout,我就不知道了。它可以std库的cout流。或者在同一个函数中前面的十行代码中有一个名为int cout = 0;的变量。或者是该文件中命名为coutstatic变量。它可能是任何东西。

现在考虑一个百万行代码库,这并不算特别大,你正在寻找一个错误,这意味着你知道这一百万行中有一行没有按照预期工作。例如,cout << 1;可能读取了一个名为coutstatic int,将其左移一位并丢弃结果。要查找错误,我必须检查这个。你能看出来吗?因此,我非常非常希望看到std::cout

这就是其中之一,如果你是一名教师,从未写过和维护过任何代码,就会觉得这是一个非常好的主意。我喜欢看到代码,(1) 我知道它是做什么的; (2) 我确信编写它的人知道它是做什么的。


7
你如何知道 "std::cout << 1" 不是读取名为 cout 的 std 命名空间中的静态 int,并将其移位一位并丢弃结果?还有,你怎么知道 "<<" 是什么意思呢 ;) ???... 看起来这个答案不是避免使用 'using' 的好数据点。 - nyholku
6
如果有人将std::cout重新定义为整数类型,那么你的问题就不是技术问题,而是社交问题——有人对你存有敌意。此外,你可能还需要检查所有头文件中是否存在#define true false等情况。 - Jeremy Friesner
6
当我看到“cout”时,我知道它是指“std::cout”。如果我错了,那就是写这段代码的人的问题,而不是我的问题 :) - Tien Do

35

我也认为这是一个不好的惯例。为什么?有一天我想,命名空间的作用是划分代码,所以我不应该把所有东西都扔进一个全局的袋子里。

然而,如果我经常使用'cout'和'cin',我会在.cpp文件中写:using std::cout; using std::cin;(从不在头文件中写,因为它会随着#include传播)。我认为没有一个理智的人会将一个流命名为coutcin。 ;)


8
这是一个使用“声明”的本地操作,与使用“指令”完全不同。 - sbi

28

这都是关于管理复杂性的。使用命名空间会引入一些不需要的东西,因此可能会使调试变得更加困难(我说可能是因为情况视具体情况而定)。到处使用 std:: 会更难阅读(有更多文本等)。

因人而异 - 尽力以最好的方式管理您的复杂性。


使用命名空间会引入一些你不想要的东西,因此可能会使调试变得更加困难(我说可能)。使用命名空间并不会“引入”任何东西。调试不受影响。 - metamorphosis
这取决于你如何定义“拉东西”。在上面的情境中,使用它意味着将std::命名空间中的所有内容视为范围内。任何标识符都可以来自该命名空间,因此阅读代码时必须考虑到这一点。如果仅在需要时引用命名空间,则会创建一种根本不存在的歧义。对于读者减少认知负荷的任何事情(例如代码的绝大部分)都是好事,相反任何增加认知负荷的事情都是坏事。因此,在结尾处我会进行免责声明。 - Preet Sangha
在这个上下文中使用“拉进来”会给人错误的印象 - 它会让人觉得无论你的意思如何,都会在程序中包含额外的命名空间声明。 我同意你所说的认知负荷。 - metamorphosis

24

一个具体的例子来澄清这个问题。想象一下,你有两个库,foobar,每个库都有自己的命名空间:

namespace foo {
    void a(float) { /* Does something */ }
}

namespace bar {
    ...
}

现在假设您在自己的程序中如下使用foobar:
using namespace foo;
using namespace bar;

void main() {
    a(42);
}

目前为止一切都很好。当你运行程序时,它会“做某事”。但是后来你更新了bar,假设它已经改变成如下:

namespace bar {
    void a(float) { /* Does something completely different */ }
}

在这一点上,您将收到一个编译器错误:

using namespace foo;
using namespace bar;

void main() {
    a(42);  // error: call to 'a' is ambiguous, should be foo::a(42)
}

因此,您需要进行一些维护工作,以澄清'a'的意思为foo::a。这是不可取的,但幸运的是,这很容易(只需在编译器标记为模糊的所有对a的调用前面添加foo::即可)。

但是,请想象另一种情况,bar发生了变化,变成了这样:

namespace bar {
    void a(int) { /* Does something completely different */ }
}

此时你对a(42)的调用突然绑定到bar::a而不是foo::a,并且不再执行“something”而是执行“something completely different”。没有编译器警告或其他提示。你的程序默默地开始执行与之前完全不同的操作。
当你使用命名空间时,就会面临这种情况的风险,这就是为什么人们不太喜欢使用命名空间的原因。命名空间中的东西越多,冲突的风险就越大,因此人们可能对使用命名空间std(由于该命名空间中的内容数量)感到更加不舒服。
最终,这是可写性与可靠性/可维护性之间的权衡。可读性也可能是一个因素,但我可以看到双方都有争议的论点。通常我会说可靠性和可维护性更重要,但在这种情况下,你将不断为一个相当罕见的可靠性/可维护性影响支付可写性成本。这个“最好”的权衡取决于你的项目和你的优先事项。

1
第二种情况让我下定决心。再次没有命名空间。在引擎盖下不能有如此微妙的功能变化未被察觉。 - safe_malloc
解决这个问题的方法是允许命名空间成员被标记为版本,并且有一种方式可以通过using指令来指定它应该引入带有旧版本号标记的成员,但不包括那些带有新版本号标记的成员。如果在程序员编写using指令时,最新版本的库是147,则程序将在使用指令中包含该版本号,以后添加的任何函数都会被标记为更高的数字,指定版本147的代码将继续像以前一样工作。 - supercat

24

请考虑

// myHeader.h
#include <sstream>
using namespace std;


// someoneElses.cpp/h
#include "myHeader.h"

class stringstream {  // Uh oh
};

请注意,这只是一个简单的例子。如果你有包含20个以上的文件和其他导入项,你将需要查找大量依赖项来找到问题所在。更糟糕的是,由于冲突定义,你可能会在其他模块中遇到不相关的错误。

这并不是非常可怕,但是如果你不在头文件或全局命名空间中使用它,你将节省自己很多麻烦。在非常有限的作用域中使用它可能没什么问题,但我从未因为要多打五个字符以明确函数来源而遇到过问题。


在头文件中肯定要加上,但如果“using namespace std”仅出现在实现文件中呢? - mercury0114

20
  1. 你需要能够阅读其他人所写的代码,即使这些人使用的编码风格和最佳实践不同于你。

  2. 如果只使用cout,不会有人感到困惑。但当你在许多命名空间中穿梭,并看到某个类时,可能不确定它的作用是什么,显式指定命名空间就像是一种注释。你可以一眼看出,“噢,这是一个文件系统操作”或“那是在进行网络操作”。


19
同时使用许多命名空间显然是灾难的源头,但仅使用命名空间std并且只使用std命名空间在我的看法中并不是太大的问题,因为重新定义只会发生在您自己的代码中...所以只需将它们视为保留名称的函数,如"int"或"class"即可。
人们应该停止过度关注。您的老师一直是正确的。只使用一个命名空间就可以了;这是使用命名空间的整个意义所在。您不应该同时使用多个命名空间,除非是您自己定义的。所以,重新定义不会发生。

创建碰撞并不难 - std:: 命名空间中出现了像 minendless 这样的短字符串。但是,现在 std:: 中有数千个符号,对于读者来说,知道一个他们可能不知道的新符号来自哪里是很有用的。 - Tom Swirly
std 命名空间的存在是因为人们,无论是你、你的同事还是编写你使用的中间件的人,在将函数放入命名空间方面并不总是明智的。因此,你可以导入所有的 std::,而不导入其他任何东西,同时仍然会在 std::min 和某个人以前的遗留 ::min() 之间引发冲突,这是在它被放入 std 之前的时代。 - Aiken Drum

14

命名空间是一种具有名称的范围。命名空间用于分组相关声明并将不同项保持分离。例如,两个独立开发的库可能使用相同的名称来引用不同的项,但用户仍然可以同时使用它们。

namespace Mylib{
    template<class T> class Stack{ /* ... */ };
    // ...
}

namespace Yourlib{
    class Stack{ /* ... */ };
    // ...
}

void f(int max) {
    Mylib::Stack<int> s1(max); // Use my stack
    Yourlib::Stack    s2(max); // Use your stack
    // ...
}

重复命名空间名称会分散读者和作者的注意力。因此,可以声明来自特定命名空间的名称可在不显式限定的情况下使用。例如:

void f(int max) {
    using namespace Mylib; // Make names from Mylib accessible
    Stack<int> s1(max); // Use my stack
    Yourlib::Stack s2(max); // Use your stack
    // ...
}

命名空间为不同的库和不同版本的代码管理提供了强大的工具。特别是,它们为程序员提供了在引用非本地名称时如何显式的选择。

来源:Bjarne Stroustrup 的 C++ 编程语言概述


5
非常有趣,这个答案基于Bjarne Stroustrup的指导却获得了-2分...看来当初Bjarne引入C++中这个特性时可能是一位经验不足的程序员。 - nyholku
@nyholku:请参考这里 - sbi

14

我同意其他人的看法,但我希望解决有关可读性的问题 - 您可以通过在文件、函数或类声明顶部简单地使用typedef来避免所有这些问题。

通常我会在类声明中使用它,因为类中的方法往往处理相似的数据类型(成员变量),而typedef是一个机会,可以分配一个在类上下文中有意义的名称。这实际上有助于定义类的方法时的可读性。

// Header
class File
{
   typedef std::vector<std::string> Lines;
   Lines ReadLines();
}

在实施过程中:

// .cpp
Lines File::ReadLines()
{
    Lines lines;
    // Get them...
    return lines;
}

相对于:

// .cpp
vector<string> File::ReadLines()
{
    vector<string> lines;
    // Get them...
    return lines;
}
或者:
// .cpp
std::vector<std::string> File::ReadLines()
{
    std::vector<std::string> lines;
    // Get them...
    return lines;
}

只是一个小注释,虽然typedef很有用,但我会考虑创建一个代表Lines的类,而不是使用typedef。 - eyalalonn

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