问题:为什么是"使用命名空间std";被认为是不良做法?

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

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

标签:c++,namespaces,std,using-directives,c++-faq

回答1:

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

回答2:

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

Library Foo 2.0可以引入Quux()函数,该函数对于您对Quux()的某些调用要比bar更好地匹配:: Quux()您的代码调用了多年。然后,您的 代码仍会编译 ,但是 它会悄悄地调用错误的函数 ,并且会做些什么。那真是糟透了。

请记住,std名称空间具有大量的标识符,其中很多都是非常常见的标识符(请考虑list排序字符串迭代器等),也很有可能会出现在其他代码中。

如果您认为这种情况不太可能发生:在Stack Overflow上的有一个问题在这里发生了(确切地说是错误的)在我给出此答案大约半年后,由于省略了std::前缀而调用了该函数。 此处是此类问题的另一个较新的示例。所以这是一个真正的问题。


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

十年来,该项目发展成拥有几百万行代码。由于这些讨论一遍又一遍地出现,我曾经很好奇项目中实际使用(允许的)功能范围using的频率。我找到了它的来源,只发现了使用它的一两个地方。对我来说,这表明,一旦尝试,开发人员就不会发现std::足够痛苦,即使允许每100 kLoC使用一次使用指令,即使允许使用它也是如此。


底线:明确地为所有内容加上前缀不会造成任何损害,只需很少的时间就可以适应,并且具有客观的优势。尤其是,它使代码更易于由编译器和人类读者解释,而这可能应该是编写代码时的主要目标。

回答3:

usingnamespace放在类的头文件中的问题是,它迫使想要使用您的类的任何人(包括头文件)也被"使用"(即看到其他名称空间中的所有内容。

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


请注意,有些人不同意我的这种"随意"的说法-因为尽管cpp文件中的using语句比标题中的更好(因为它不会影响包含您的头文件的人),他们认为它仍然不是(因为取决于代码,它可能会使类的实现更难以维护)。 此C ++超级常见问题解答说,

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

常见问题解答提出了两种选择:

  • 使用声明:

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

    std::cout << "Values:";
    

回答4:

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

using namespace std;
using namespace boost;

很多 Boost 功能都已进入C语言++ 0x标准,并且Visual Studio 2010具有很多C ++ 0x功能,因此突然这些程序没有编译。

因此,避免使用名称空间X;是一种面向未来的方法,一种确保对使用中的库和/或头文件进行更改的方法不会破坏程序。

回答5:

简短版本:不要在头文件中使用全局using声明或指令。随时在实现文件中使用它们。这是草药萨特 Andrei Alexandrescu 必须在 C ++编码标准(强调这一点是我的意思):

摘要

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

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

讨论

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

回答6:

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

回答7:

请勿全局使用

仅当全局使用时,它才被视为"不良"。因为:

  • 您使正在编程的名称空间混乱。
  • 当您使用许多 xyz 名称空间
  • 时,读者将很难看到特定标识符的来源。
  • 对于源代码的 other 读者来说,无论是什么,对于最经常阅读它的人:自己都是如此。一两年后再回来看看...
  • 如果仅谈论使用命名空间std 的,则可能不知道所抓取的所有内容,并且当您添加另一个#include或移至新内容时您可能不知道的C ++版本名称冲突。

您可以在本地使用它

继续并在本地(几乎)自由使用它。当然,这可以防止您重复std::,而且重复也很糟糕。

在本地使用它的习惯用法

在C ++ 03中,有一个习惯用法-样板代码-用于为您的类实现swap函数。建议您实际上使用本地使用命名空间std或至少使用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,即voidstd::swap(int,int)
  • li>
  • 如果实现了过载voidswap(Child&,Child&),则编译器将选择它。
  • 如果您没有具有该重载,则编译器将使用voidstd::swap(Child&,Child&)并尽力交换它们。

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

回答8:

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

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

回答9:

另一个原因是惊喜。

如果我看到cout< ,而不是std::cout< ,我想:这是什么cout?是普通的cout吗?有什么特别的吗?

回答10:

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

有经验的程序员还尝试避免对源文件中的名称进行完全限定。造成这种情况的一个次要原因是,除非有充分的理由,否则当编写的代码较少时,编写更多的代码就不太好。造成这种情况的主要原因是关闭了依赖于参数的查询(ADL)。

这些充分理由是什么?有时,程序员明确希望关闭ADL,而有时他们又想消除歧义。

所以下面的方法是可以的:

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

回答11:

我同意不应在全球范围内使用它,但是在本地使用(例如在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

}

在此示例中,我们解决了由其组成引起的潜在名称冲突和歧义。

在那里显式声明的名称(包括通过使用声明声明的名称,例如His_lib::String)优先于通过使用伪指令(usingnamespaceHer_lib )。

回答12:

我也认为这是一种不好的做法。为什么?只是有一天,我认为名称空间的功能是划分内容,所以我不应该将所有内容都放入一个全局包中来破坏它。

但是,如果我经常使用'cout'和'cin',我会写:usingstd::cout;在.cpp文件中使用std :: cin; (在头文件中不会使用#include进行传播)。我认为没有人理智地将流命名为coutcin。 ;)

回答13:

很高兴看到代码并知道它的作用。如果我看到std::cout,我知道这就是std库的cout流。如果我看到cout,那么我不知道。它可以是std库的cout流。或者在同一函数中可能有一个intcout=0;高十行。或该文件中名为coutstatic变量。可能是任何东西。

现在采用一百万行代码库,这不是特别大,并且您正在寻找错误,这意味着您知道这一百万行中有一行没有按预期执行做。 cout<<1;可以读取名为coutstaticint,将其向左移动一位,然后丢弃结果。寻找错误,我必须检查一下。您能看到我真的很喜欢看std::cout吗?

如果您是一名老师,而不必为维持生活而编写和维护任何代码,那么这就是其中的一个主意。我喜欢在以下地方查看代码:(1)我知道它的作用;并且,(2)我相信编写它的人知道它的作用。

回答14:

所有这些都是关于管理复杂性的。使用名称空间会将您不想要的东西拉进去,从而可能使调试变得更加困难(我说可能)。在所有地方使用std ::很难阅读(更多文字和其他内容)。

课程的马匹-尽可能地管理自己的复杂性并保持能力。

回答15:

考虑

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


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

class stringstream {  // Uh oh
};

请注意,这是一个简单的示例。如果您的文件包含20个include和其他导入,则将需要大量的依赖关系来解决问题。更糟糕的是,根据冲突的定义,您可能在其他模块中得到不相关的错误。

这并不可怕,但是您可以通过不在头文件或全局名称空间中使用它来避免麻烦。在非常有限的范围内执行此操作可能没问题,但是我从来没有遇到过键入额外的五个字符来弄清楚我的功能来自哪里的问题。

回答16:

  1. 您需要能够阅读由与您有不同风格和最佳实践观点的人编写的代码。

  2. 如果您仅使用cout,没有人会感到困惑。但是,当您有很多名称空间在四处游荡,并且看到此类时,您不确定它的作用是什么,将名称空间显式用作某种注释。乍看之下,"哦,这是一个文件系统操作"或"正在做网络工作"。

回答17:

同时使用多个名称空间显然是灾难的根源,但是在我看来,仅使用名称空间std和仅使用名称空间std并不是一件大事因为重新定义只能通过您自己的代码进行...

因此,只需将它们用作" int"或" class"之类的保留名称即可。

人们应该不再对此感到厌烦。你的老师一直都是对的。只需使用一个名称空间;这就是首先使用名称空间的全部意义。您不应同时使用多个。除非是你自己的。因此,再次定义将不会发生。

回答18:

我在这里同意其他观点,但是我想解决有关可读性的问题-您只需在文件,函数或类声明的顶部使用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;
}

回答19:

名称空间是一个命名范围。命名空间用于对相关声明进行分组,并使单独的项目保持独立。例如,两个单独开发的库可能使用相同的名称来引用不同的项目,但是用户仍然可以使用这两个库:

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 ++编程语言概述

回答20:

一个具体的例子来澄清这个问题。假设您遇到这样一个情况,您有两个库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::)。

但是,请设想另一种情况,条形改为改为:

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

这时您对a(42)的调用突然绑定到bar::a而不是foo::a做"某事"会做"完全不同的事"。没有编译器警告或其他任何内容。您的程序只是默默地开始做一些与以前完全不同的事情。

使用名称空间时,您面临这样的风险,这就是为什么人们对使用名称空间感到不舒服的原因。命名空间中的内容越多,发生冲突的风险就越大,因此与其他命名空间相比,使用命名空间std(由于该命名空间中事物的数量),人们可能会更加不舒服。

最终,这是可写性与可靠性/可维护性之间的权衡。可读性也可能是其中的原因,但我可以看到无论哪种方式都存在争议。通常,我会说可靠性和可维护性更为重要,但是在这种情况下,您将不断支付可写性成本,以实现相当罕见的可靠性/可维护性影响。最佳的权衡将取决于您的项目和您的优先级。

回答21:

一个示例,其中使用命名空间std会由于count的歧义而引发编译错误,这也是算法库中的函数。

#include <iostream>

using namespace std;

int count = 1;
int main() {
    cout << count << endl;
}

回答22:

我认为这不一定在所有情况下都是不好的做法,但是在使用它时需要小心。如果要编写库,则可能应将作用域解析运算符与名称空间一起使用,以防止您的库与其他库发生冲突。对于应用程序级代码,我认为没有任何问题。

回答23:

这不会使您的软件或项目性能变差。在源代码的开头包含名称空间也不错。 usingnamespacestd指令的包含内容根据您的需求以及开发软件或项目的方式而有所不同。

命名空间std包含C ++标准函数和变量。当您经常使用C ++标准函数时,此命名空间很有用。

正如在页面中所提到的:< / p>

使用名称空间std的语句通常被认为是不好的做法。此语句的替代方法是,每次我们声明类型时,都使用范围操作符(::)指定标识符所属的名称空间。

并参见此意见

大量使用命名空间并确定不会冲突时,在源文件中使用"使用命名空间std"是没有问题的。

有人曾说过,在源文件中包含usingnamespacestd是不正确的做法,因为您要从该命名空间调用所有函数和变量。当您要定义一个与namespacestd中包含的另一个函数同名的新函数时,您将使该函数过载,并且由于编译或执行而可能产生问题。它不会像您期望的那样编译或执行。

正如在页面中所提到的:< / p>

尽管该语句使我们不必键入std ::,但每当我们希望访问std名称空间中定义的类或类型时,它都会将std名称空间的全部导入到程序的当前名称空间中。让我们举几个例子来理解为什么这可能不是一件好事

...

现在,在以后的开发阶段,我们希望使用在某个名为" foo"的库中自定义实现的cout的另一个版本(例如)

...

请注意,cout指向哪个库有歧义?编译器可能会检测到此情况,而不编译程序。在最坏的情况下,该程序可能仍会编译,但会调用错误的函数,因为我们从未指定标识符属于哪个名称空间。

回答24:

使用不合格的导入标识符,您需要外部搜索工具(例如 grep )来查找声明标识符的位置。这使得关于程序正确性的推理更加困难。

回答25:

取决于它所在的位置。如果它是公共头,那么您正在通过将其合并到全局名称空间中来减小名称空间的值。请记住,这可能是使模块全局化的一种好方法。

回答26:

这是一种不良做法,通常称为全局名称空间污染。当多个名称空间具有相同的带有签名的函数名称时,可能会出现问题,然后编译器决定要调用哪个名称将是模棱两可的,而在通过函数调用(例如)指定名称空间时,可以避免所有这些情况.std :: cout 。希望这可以帮助。 :)

回答27:

"为什么要'使用命名空间标准;'被认为是C ++中的不良做法?"

我反过来说:为什么输入另外五个字符被某些人觉得麻烦?

考虑例如编写一个数字软件。当"向量"是问题域最重要的概念之一时,为什么我甚至考虑通过将通用的" std :: vector"削减为"向量"来污染全局命名空间?

回答28:

我同意其他人的观点-它要求名称冲突,含糊不清,然后事实是它不够明确。尽管可以看到using的用法,但我个人的偏好是限制它。我也会强烈考虑其他人指出的内容:

如果您想找到一个可能是一个非常普通的名称的函数名,但只想在std命名空间中找到它(或者相反,您想更改所有 not 命名空间std,命名空间X等),那么您打算怎么做呢?

您可以编写一个程序来做到这一点,但是花时间在项目本身上而不是编写一个程序来维护您的项目会更好吗?

我个人实际上并不介意std::前缀。我不喜欢它,而不是喜欢它。我不知道这是否是因为它是显式的,并对我说:"这不是我的代码……我正在使用标准库",或者是否还有其他用途,但我认为它看起来更好。考虑到我最近才刚接触C ++(这可能很奇怪)(使用和仍然使用C和其他语言的时间更长,而C是我一直以来最喜欢的语言,就在汇编之上)。

还有另一件事,尽管它与上述内容以及其他指出的内容有些相关。尽管这可能是不好的做法,但有时我会为标准库版本保留std::name,并为程序特定的实现保留名称。是的,的确可以咬你,也可以咬你,但是一切都归结于我从头开始这个项目,而且我是唯一的程序员。示例:我重载了std::string并将其命名为string。我有一些有益的补充。我之所以这么做,部分原因是我的C和Unix(+ Linux)倾向于使用小写字母。

除此之外,您可以具有名称空间别名。这是一个可能未提及的有用示例。我使用C ++ 11标准,尤其是libstdc ++。好吧,它没有完整的std::regex支持。当然,它可以编译,但是会引发异常,因为它是程序员的错误。但这缺乏执行力。

所以这是我解决的方法。安装Boost的regex,并将其链接。然后,我执行以下操作,以便在libstdc ++完全实现它时,我只需要删除此块,并且代码保持不变即可:

namespace std
{
    using boost::regex;
    using boost::regex_error;
    using boost::regex_replace;
    using boost::regex_search;
    using boost::regex_match;
    using boost::smatch;
    namespace regex_constants = boost::regex_constants;
}

我不会争论那是一个坏主意。但是,我会争辩说,它可以使 my 项目保持干净,同时又要使其具体化:确实,我必须使用Boost,但是我正在使用它,例如libstdc ++最终将拥有它。是的,从一开始就开始自己的项目并从一个标准(...)开始对帮助维护,开发以及该项目所涉及的一切都非常漫长!

只是要澄清一点:我实际上不认为在 STL 是故意的,更具体地说是代替。对于我来说,字符串是个例外(请忽略此处的第一个,第二个或第二个,如果需要,请双关),因为我不喜欢" String"的概念。

就目前而言,我仍然非常偏爱C和偏爱C ++。保留细节,我所做的大部分工作都更适合C(但这是一个很好的练习,也是使自己成为一个好方法的一种方法。a。学习另一种语言,并且b。尽量不要偏向于对象/类/等,这也许更好地表述了更少的胸襟,更少的傲慢和更多的接受。)但是 有用的是已经有人提出的建议:我确实使用list(它是相当通用的,不是吗?),并排序(同一件事)命名两个,如果出现,则会引起名称冲突我要使用命名空间std; 进行编码,因此,我更希望在控制方面是特定的,并且知道如果我打算将其用作标准用途,则必须指定它。简单地说:不允许假设。

并且要使Boost的正则表达式成为std的一部分。我这样做是为了将来的集成,并且-再次,我完全承认这是偏见-我认为这不像boost::regex::...那样丑陋。确实,那对我来说是另一回事。在C ++中,在外观和方法上我还有很多事情要完全接受(另一个示例:可变参数模板与var参数[尽管我承认可变参数模板非常有用!])。即使是那些我确实接受过的人也很困难,而且他们仍然有问题。

回答29:

根据我的经验,如果您有多个使用cout的库,但是出于不同的目的,您可能使用了错误的cout

例如,如果我输入,使用命名空间std进行使用命名空间otherlib;并仅键入cout(碰巧而不是std::cout(或'otherlib::cout'),您可能使用了错误的代码,并得到了错误。使用std::cout更为有效。

回答30:

要回答您的问题,我实际上是这样看的:很多程序员(不是全部)都调用命名空间std。因此,应该养成不使用与名称空间std相同或具有相同名称的事物的习惯。确实可以做到这一点,但与严格说来可能出现的连贯单词和假名的数量相比,并没有那么多。

我的意思是说真的...说"不要依靠这个存在"只是让您设置为不存在。您将不断遇到借用代码片段并不断修复它们的问题。只需将用户定义和借用的内容限制在应有的范围内,并尽量避免与全局变量一起使用(诚实地说,全局变量几乎永远是"现在编译,以后再理"的最后手段)。确实,我认为这是您老师的不明智建议,因为使用std既可用于" cout"也可用于" std :: cout",但不使用std仅可用于" std :: cout"。您不一定总是有幸编写自己的代码。

注意:在您真正了解编译器的工作方式之前,不要过多地关注效率问题。有了一点编码的经验,您就无需了解太多关于它们的知识,而是可以了解他们能够将好的代码概括为简单的东西的程度。就像您用C编写整个程序一样简单。良好的代码仅是需要的复杂程度。

回到顶部