c++ - 命名空间 - typedef namespace




为什么“使用命名空间std”被认为是不好的做法? (20)

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

为什么using namespace std被视为不好的做法? 是否效率低或者是否存在声明模糊变量(与std命名空间中的函数具有相同名称的变量)的风险? 它会影响性能吗?


不要在全球范围内使用它

只有在全球使用时才被视为“坏”。 因为:

  • 你混乱了编程的命名空间。
  • 当你使用很多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的实现被更改为找到潜在的重载并选择它。


  1. 你需要能够阅读那些与你有不同风格和最佳实践意见的人所编写的代码。

  2. 如果你只使用cout,没有人会感到困惑。 但是当你有大量的命名空间飞来飞去并且你看到这个类而你并不完全确定它的作用时,将命名空间显式化作为一种​​评论。 乍一看,你可以看到'哦,这是一个文件系统操作'或'那就是做网络的东西'。


使用命名空间std的示例由于计数的模糊性而引发了编译错误,这也是算法库中的函数。

#include <iostream>

using namespace std;

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

另一个原因是惊喜。

如果我看到cout << blah ,而不是std::cout << blah

我想这个cout什么? 这是正常的cout吗? 这是特别的吗?


在类的头文件中using namespace的问题在于它强制任何想要使用您的类(通过包含头文件)的人也可以“使用”(即看到所有内容)这些其他命名空间。

但是,您可以随意在(私有)* .cpp文件中放置using语句。

请注意,有些人不同意我的说法“感觉自由”,因为虽然cpp文件中的using语句比标题中的更好 (因为它不会影响包含头文件的人),但他们认为它仍然存在不好 (因为根据代码,它可能使类的实现更难维护)。 这个FAQ主题说,

using-directive存在于遗留C ++代码中,并且可以简化向命名空间的转换,但您可能不应该定期使用它,至少不应该在新的C ++代码中使用它。

FAQ提出了两种选择:

  • 使用声明:

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

    std::cout << "Values:";
    

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

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


我也认为这是一种不好的做法。 为什么? 只有一天,我认为命名空间的功能是划分东西,所以我不应该把所有东西扔到一个全球包中来破坏它。 但是,如果我经常使用'cout'和'cin',我会写: using std::cout; using std::cin; using std::cout; using std::cin; 在cpp文件中(从不在头文件中,因为它与#include传播)。 我认为没有人会说出一个流coutcin 。 ;)


我同意格雷格所写的一切,但我想补充一点: 它甚至可能比格雷格说的更糟糕!

Library Foo 2.0可以引入一个函数Quux() ,这个函数明确地比你的Quux()调用更好,而不是你的代码多年来调用的bar::Quux() 。 然后你的代码仍然编译 ,但它默默地调用错误的函数并且知道神知道什么。 事情就像事情一样糟糕。

请记住, std命名空间有很多标识符,其中很多都是非常常见的(思想listsortstringiterator等),它们很可能也出现在其他代码中。

如果你认为这不太可能:在我给出这个答案大约半年之后,在上有一个问题 ,其中几乎发生了这种情况(由于省略了std::前缀而导致错误的函数被调用)。 Here是另一个最近这样一个问题的例子。 所以这是一个真正的问题。

这里还有一个数据点:很多年前,我还常常发现它必须在标准库中加入std::前缀。 然后我在一个项目中工作,在开始时决定除了函数范围之外禁止using指令和声明。 你猜怎么着? 我们大部分时间都花了很长时间才习惯编写前缀,经过几周后,我们大多数人甚至同意它实际上使代码更具可读性 。 这是有原因的: 你喜欢更短或更长的散文是主观的,但前缀客观上增加了代码的清晰度。 不仅是编译器,而且您也发现更容易看到引用了哪个标识符。

十年来,该项目增长了数百万行代码。 由于这些讨论一次又一次地出现,我曾经很好奇在项目中using (允许)实际使用的功能范围的频率。 我找到它的来源,只找到了一两个地方使用它。 对我而言,这表明,一旦尝试过,开发人员即使在允许使用指令的情况下,即使每100kLoC使用指令也不会发现std:: painful。

底线:明确地为所有内容添加前缀不会造成任何伤害,很少习惯,并具有客观优势。 特别是,它使编码器和人类读者更容易理解代码 - 这应该是编写代码时的主要目标。


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

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

using namespace std;
using namespace boost;

很多Boost功能都进入了C ++ 0x标准,Visual Studio 2010有很多C ++ 0x功能,所以突然间这些程序没有编译。

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


简短版本:不要在头文件中使用全局使用声明或指令。 随意在实现文件中使用它们。 以下是Herb Sutter和Andrei Alexandrescu在C ++编码标准中对此问题所说的内容(重点强调是我的):

摘要

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

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

讨论

简而言之:在#include指令之后,你可以而且应该在你的实现文件中使用声明和指令来使用命名空间,并且感觉良好。 尽管反复断言,使用声明和指令的命名空间并不是邪恶的,并且它们不会破坏命名空间的目的。 相反,它们是使命名空间可用的原因


考虑

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


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

class stringstream {  // uh oh
};

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

这并不可怕,但你不会在头文件或全局命名空间中使用它来避免头痛。在非常有限的范围内完成它可能是正常的,但我输入额外的5个字符以解释我的功能来自哪里我从未遇到过问题。


这根本与性能无关。 但请考虑一下:您正在使用两个名为Foo和Bar的库:

using namespace foo;
using namespace bar;

一切正常,你可以Quux()从Bar和Foo调用Blah() 。 但是有一天你升级到新版本的Foo 2.0,它现在提供了一个名为Quux()的函数。 现在你遇到了冲突:Foo 2.0和Bar都将Quux()导入你的全局命名空间。 这将需要一些努力来修复,特别是如果函数参数碰巧匹配。

如果你曾经使用过foo::Blah()bar::Quux() ,那么引入foo::Quux()将是一个非事件。


“为什么'使用命名空间std;' 在C ++中被认为是一种不好的做法?“

我把它反过来说:为什么输入5个额外的字符被一些人认为是麻烦的?

考虑例如编写一个数字软件,当“向量”是问题领域最重要的概念之一时,为什么我会考虑通过将一般的“std :: vector”切换为“向量”来污染我的全局命名空间?


为了回答你的问题,我实际上以这种方式看待它:许多程序员(不是全部)调用命名空间std。因此,人们应该养成不使用与命名空间std中的名称相同或使用相同名称的东西。这是一个很大的问题,但与严格来说可能提出的可能连贯的单词和假名的数量相比,并没有那么多。

我的意思是真的......说“不要依赖于存在”只是让你依赖它不存在。你经常会有问题借用代码片段并不断修复它们。只需保留用户定义和借用的东西,因为它们应该是有限范围的,并且非常不使用全局变量(诚然,全局变量应该是“现在编译,后来理智”的最后手段)。真的,我认为这是你老师的坏建议,因为使用std将同时适用于“cout”和“std :: cout”,但不使用std只适用于“std :: cout”。你并不总是有幸写下你自己的代码。

注意:在实际了解编译器的工作原理之前,不要过分关注效率问题。通过一些编码经验,在您意识到他们能够将优秀代码概括为简单的东西之前,您不必了解它们。每一点都很简单,好像你用C编写了整个东西。好的代码只是它需要的复杂。


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


在所有条件下,我认为这不一定是不好的做法,但是在使用它时需要小心。如果您正在编写库,则可能应该使用范围分辨率运算符和命名空间来保持库与其他库的对接。对于应用程序级代码,我没有看到任何错误。


它不会使您的软件或项目性能变差,在源代码开头包含命名空间也不错。包含using namespace std指令会根据您的需要以及您开发软件或项目的方式而有所不同。

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

page

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

并看到这个意见

当您大量使用命名空间时,在源文件中使用“using namespace std”是没有问题的,并确保不会发生任何冲突。

有些人说,using namespace std在源文件中包含这是一个不好的做法,因为你从该命名空间调用了所有的函数和变量。当你想要定义一个与namespace std你所包含的另一个函数同名的新函数时,你会重载函数,并且它可能会因编译或执行而产生问题。它不会像你期望的那样编译或执行。

page

虽然每当我们希望访问std命名空间中定义的类或类型时,该语句都会使我们无法输入std ::,它会将整个std命名空间导入到程序的当前命名空间中。让我们举几个例子来理解为什么这可能不是一件好事

...

现在处于开发的后期阶段,我们希望使用另一个版本的cout,它在一些名为“foo”的库中自定义实现(例如)

...

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


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

例如,如果我键入,using namespace std;并且using namespace otherlib;只键入cout(恰好在两者中),而不是std::cout(或'otherlib::cout'),您可能使用错误的一个,并获得错误,它使用起来更加有效和高效std::cout


这取决于它的位置。如果它是一个公共头,那么你通过将它合并到全局命名空间来减少命名空间的值。请记住,这可能是制作模块全局变量的一种巧妙方法。





c++-faq