为什么是 “使用命名空间标准”;被认为是不良做法?

其他人告诉我, using namespace std;编写using namespace std;在代码中是错误的,我应该直接使用std::coutstd::cin代替。

为什么using namespace std;认为是不好的做法?是效率低下还是冒着声明模棱两可的变量(与std名称空间中的函数具有相同名称的变量)的风险?它会影响性能吗?

答案

这根本与性能无关。但是考虑一下:您正在使用两个名为 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()的引入将是非事件。

我同意Greg 撰写的所有内容,但我想补充一点: 它甚至比 Greg 所说的还要糟糕!

库 Foo 2.0 可以引入一个函数Quux()Quux()您对Quux()某些调用的匹配度要好于您Quux()调用的bar::Quux()bar::Quux() 。然后,您的代码仍然可以编译 ,但是它会静默地调用错误的函数 ,并且知道什么。事情会变得糟透了。

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

如果您认为这不太可能,请执行以下操作:在我给出答案的半年后,Stack Overflow 上有一个问题几乎完全在此发生(由于省略了std::前缀而调用了错误的函数)。 是此类问题的另一个最新示例。所以这是一个真正的问题。


这是另一个数据点:许多年前,我还发现必须在标准库中的所有内容前加上std::作为前缀,这很烦人。然后,我在一个项目中工作,该项目一开始就决定禁止using指令和声明(函数范围除外)。你猜怎么了?我们大多数人花了几周的时间来习惯于编写前缀,而又过了几周,我们大多数人甚至同意它实际上使代码更具可读性 。这样做的原因是: 您喜欢散文是短还是长是主观的,但是前缀客观地增加了代码的清晰度。不仅是编译器,而且您也发现查找引用的标识符更加容易。

在十年中,该项目增长到拥有几百万行代码。由于这些讨论上来一次又一次,我曾经很好奇多久(允许)函数范围using实际上是在项目中使用。我为它找到了源,只发现了使用它的一两个地方。对我来说,这表明,一旦尝试,开发人员就找不到std:: ::: 即使在允许使用的情况下,即使每 100 kLoC 使用一次使用指令也不足够痛苦。


底线:明确地为所有内容加上前缀不会造成任何损害,几乎不需要习惯,并且具有客观优势。特别是,它使代码更易于由编译器和人类读者解释,并且这可能是编写代码时的主要目标。

在类的头文件中using namespace的问题在于,它会迫使想要使用您的类的任何人(包括头文件)也 “使用”(即查看所有其他名称空间)。

但是,您可以随时在(专用)*。cpp 文件中使用 using 语句。


请注意,有些人不同意我这样说的 “随意”- 因为尽管 cpp 文件中的using语句比标头中的更好 (因为它不会影响包含您的头文件的人),但他们认为它仍然不好 (因为取决于代码,这可能会使类的实现更加难以维护)。 这个 C ++ Super-FAQ 条目说,

using 指令适用于旧版 C ++ 代码,并可以简化向名称空间的过渡,但是您可能不应该定期使用它,至少在新的 C ++ 代码中不应该使用。

FAQ 提出了两种选择:

  • 使用声明:

    using std::cout; // a using-declaration lets you use cout without qualification
    cout << "Values:";
  • 只需输入 std ::

    std::cout << "Values:";

我最近遇到了有关Visual Studio 2010的投诉。事实证明,几乎所有源文件都具有以下两行:

using namespace std;
using namespace boost;

许多Boost功能进入 C ++ 0x 标准,而 Visual Studio 2010 具有很多 C ++ 0x 功能,因此突然这些程序没有编译。

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

简短版本:不要在头文件中使用 global using声明或指令。随时在实现文件中使用它们。这是赫伯 · 萨特Herb Sutter)安德烈 · 亚历山大Andrei Alexandrescu)C ++ 编码标准中对这个问题所要说的(强调点是我的意思):

摘要

命名空间的使用是为了您的方便,而不是您要施加给其他人:切勿在 #include 指令之前编写 using 声明或 using 指令。

结论:在头文件中,请勿使用指令或声明来编写名称空间级别;相反,显式命名空间限定所有名称。 (第二条规则从第一条开始,因为标头永远不知道其他标头 #includes 可能会出现在它们之后。)

讨论区

简而言之:您可以并且应该在 #include 指令之后的实现文件中自由使用使用声明和指令的名称空间,并对此感到满意。 尽管反复断言相反,使用声明和指令的名称空间并不是邪恶的,它们不会破坏名称空间的目的。相反,它们是使命名空间可用的原因

不应在全局范围内使用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))。

请勿在全球范围内使用

仅在全局使用时才被视为 “不良”。因为:

  • 您使正在编程的名称空间混乱。
  • 当您使用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(...)`
}

这具有以下魔力:

  • 编译器将选择std::swap作为value_ ,即void std::swap(int, int)
  • 如果实现了重载void swap(Child&, Child&)则编译器将选择它。
  • 如果没有这种重载,编译器将使用void std::swap(Child&,Child&)并尝试最好地交换它们。

使用 C ++ 11,就没有理由再使用此模式了。 std::swap的实现已更改为找到潜在的重载并选择它。

如果导入正确的头文件,则突然在全局范围内使用hexleftpluscount等名称。如果您不知道std::包含这些名称,这可能会令人惊讶。如果您还尝试在本地使用这些名称,则可能导致相当混乱。

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

另一个原因是惊喜。

如果我看到cout << blah ,而不是std::cout << blah我想:这是cout吗?难道是正常cout ?有什么特别的吗?

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

经验丰富的程序员还尝试避免在源文件中使用完全限定的名称。造成这种情况的一个次要原因是, 除非有充分的理由,否则在编写更少的代码时就编写更多的代码并不是很优雅。这样做的主要原因是要关闭基于参数的查找(ADL)。

这些好的理由是什么?有时,程序员明确希望关闭 ADL,而其他时候则想消除歧义。

所以以下是可以的:

  1. 函数实现中的函数级使用指令和使用声明
  2. 源文件内的源文件级使用声明
  3. (有时)源文件级别的使用指令