ChatGPT解决这个技术问题 Extra ChatGPT

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

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

不要忘记你可以这样做:“使用 std::cout;”这意味着您不必键入 std::cout,但不要同时引入整个 std 命名空间。
在头文件的文件范围内使用“使用命名空间标准”特别糟糕。毕竟包含文件范围的源文件 (*.cpp) 中使用它并没有那么糟糕,因为它的效果仅限于单个翻译单元。在函数或类中使用它的问题更小,因为它的效果仅限于函数或类范围。
我不鼓励使用 using 指令,但对于特定的命名空间,如 std::literals::chrono_literalsPoco::Data:KeywordsPoco::Units 和处理文字或可读性技巧的东西。每当它在头文件或实现文件中时。我猜在函数范围内可能没问题,但除了文字和其他东西之外,它没有用。
@Jon:这与命名空间 std 无关。我的重点是“在头文件的文件范围内”。把它作为一个建议:不要在头文件的文件范围内使用“使用命名空间”(std 或其他)。在实现文件中使用它是可以的。很抱歉模棱两可。
仅在标题中被认为是不好的做法。在其他地方没有包含的源文件(即 cpp 文件)中是可以的。请参阅下面的@mattnewport 的答案。 stackoverflow.com/a/26722134/125997

M
Mateen Ulhaq

考虑两个名为 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() 的引入就不会发生。


我一直很喜欢 Python 的“import big_honkin_name as bhn”,所以你可以只使用“bhn.something”而不是“big_honkin_name.something”——真的减少了打字。 C++有类似的东西吗?
@Pax 命名空间 io = boost::filesystem;
我认为说这是“一些努力修复”是夸大其词。您将没有新的 foo::Quux 的实例,因此只需使用 bar::Quux 消除所有当前使用的歧义。
任何明智的人会创建一个其非限定名称与 std 类型冲突的类型的库吗?
@erikkallen:std lib 采用了数百个(甚至数千个)名称,其中许多非常流行和常见(errorlistsort),IIRC 是放置它的一个重要原因进入自己的命名空间。
M
Mateen Ulhaq

情况可能会变得更糟,比Greg wrote更糟!

Library Foo 2.0 可以引入一个函数 Quux(),它对于您对 Quux() 的某些调用而言无疑比您的代码多年来调用的 bar::Quux() 更匹配。然后你的代码仍然可以编译,但是它默默地调用了错误的函数并且天知道了。这几乎是最糟糕的事情了。

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

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

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

十年后,该项目发展到拥有数百万行代码。由于这些讨论一次又一次地出现,我曾经很好奇(允许的)函数范围 using 在项目中实际使用的频率。我搜索了它的来源,只找到了一两打使用它的地方。对我来说,这表明,一旦尝试,即使在允许使用的地方,即使每 100 kLoC 使用一次,开发人员也不会觉得 std:: 痛苦到足以使用 using 指令。

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


不同意读者对 foo::bar() 的解释可能意味着来自命名空间 foo 的函数 bar 或来自类 foo 的静态函数。
@convert 为什么有人会调用类 foo 而不是 Foo?静态方法也应该称为 Foo::Bar 而不是 Foo::bar。这就是为什么人们认为约定是一件好事。
@convert 这是标准库中的常见做法。大多数(我所知道的)C++ 编码约定都推荐大写的类。我所知道的超过一半的约定都推荐大写的静态方法。即使您有一些既不符合要求的巫毒编码约定,将 foo::bar 作为静态方法仍然不是反对解释点的论据。该函数/方法所属的位置仍然更清楚,如果您给您的类起一个好名字,那么仍然很清楚是一个类而不是命名空间。
@convert 是的,这正是我要说的。我的意见可能对你没有什么价值,但这甚至是 Stroustrups 和 Sutters 的意见:C++ Core Guidelines - 顺便说一句。我们应该停止用这个 12.5 岁的答案玩死灵法师......
@convert:“停止玩死灵法师”这不是聊天框,也不是组织节日的论坛,日历时间本身就是一个因素。这是一个知识库,其中仅日期无关紧要,相关性和一致性等问题很重要。这个话题(问题)既有答案,也有答案。所以,“我们应该停止”误解 SO 是什么。 (注意:您实际上在这里得到了奖励,以一种有用的方式更新旧项目。)
M
Marc.2377

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

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

请注意,有些人不同意我这样说“随意”——因为尽管 cpp 文件中的 using 语句比标题中的更好(因为它不会影响包含你的头文件),他们认为它仍然不是(因为根据代码,它可能会使类的实现更难维护)。 This C++ Super-FAQ entry 说,

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

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

使用声明: using std::cout; // using-declaration 允许您使用 cout 没有限定 cout << "Values:";

只需输入 std:: std::cout << "Values:";


当然,您也不应该假设全局 cout 的状态,以免有人拥有 std:cout << std::hex 并且事后 std::restore_cout_state 失败。但那完全是另一个胖子。
“但是,您可以随意在您的(私有)*.cpp 文件中添加 using 语句。”如果未来的开发团队决定更改翻译单元方案,例如通过 UnityBuilds 怎么办?毫无疑问,您最终会遇到可怕的未定义行为。
虽然对头文件的担忧是有道理的,但由于包含的方式可能会产生副作用,我觉得它们不适用于 cpp 文件。让我们看看几乎所有其他编程语言都会发生什么。例如,当您在 Java 中编码时,您几乎总是从您使用的包中导入每个符号——尤其是标准包。这意味着您几乎永远不会期望 String、List、Map 等的竞争和冲突实现。我知道的其他语言也会发生同样的情况。这是合理的国际海事组织,我们应该让生活变得轻松而不是艰难。
如果一个团队迁移到统一构建,它将不得不删除 using 关键字并哭泣,因为使用 stdlib 而不使用是一种痛苦。但是,如果您依赖 Qt,这没关系,因为 Qt 不使用命名空间(祝福他们)。尽管如此,统一构建是一个边缘案例。
…给你。另一方面,对于 C++ 生态系统的绝大多数人,包括 C++ 委员会、经验丰富的 C++ 开发人员的共同智慧和 C++ 语言的创造者本人,这不仅是一种选择,而且是推荐的选择。
P
Peter Mortensen

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

using namespace std;
using namespace boost;

很多 Boost 功能正在进入 C++0x 标准,而 Visual Studio 2010 有很多 C++0x 功能,所以突然这些程序无法编译。

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


这个。 Boost 和 std 有很多重叠 - 特别是从 C++11 开始。
我这样做过一次,并以艰难的方式吸取了教训。现在,我从不在函数定义之外使用 using,也很少使用 using namespace
我个人永远不会使用 boost,因为它是我见过的最糟糕的 C++ API。如果使用命名空间标准,我还会遇到哪些问题?
@convert 理论上任何库现在或将来都可能与 std 发生冲突。如其他答案中所述,std 包含许多常用名称,例如列表和错误。 Boost 只是突出了这个问题,因为它现在受到了影响。调用 using 撤消了应该修复的命名空间。小心它。
C
Community

短版:不要在头文件中使用全局 using 声明或指令。随意在实现文件中使用它们。以下是 Herb SutterAndrei AlexandrescuC++ Coding Standards 中对这个问题的看法(粗体表示强调是我的):

总结 命名空间使用是为了您的方便,而不是让您强加于他人:切勿在#include 指令之前编写 using 声明或 using 指令。推论:在头文件中,不要写命名空间级别的 using 指令或 using 声明;相反,明确命名空间限定所有名称。 (第二条规则从第一条开始,因为标头永远无法知道其他标头#includes 可能出现在它们之后。) 讨论简而言之:您可以并且应该在#include 指令之后的实现文件中自由地使用命名空间使用声明和指令,并且感觉很好。尽管反复断言相反,使用声明和指令的命名空间并不是邪恶的,它们不会破坏命名空间的目的。相反,它们使命名空间可用。


这里只是另外一位程序员的意见,但虽然我 100% 同意 using 一词不应出现在标题中的说法,但我并不相信将 using namespace xyz; 放置在代码中任何位置的免费许可,尤其是如果 xyzstd。我使用 using std::vector; 形式,因为它只会将命名空间中的单个元素拉入伪全局范围,因此导致冲突的风险要小得多。
@Lightness Races in Orbit 你当然有权发表你的意见。如果有人尝试解释您为什么不同意此答案中给出的建议,那会更有帮助。如果“使用”名称空间不好,那么了解名称空间的意义尤其重要?为什么不将事物命名为 std_cout 而不是 std::cout ... C++/命名空间的创建者在他们费心创建它们时一定有一些想法。
@nyholku:不需要-其他大多数答案都给出了与我相同的原因。另外请不要犹豫,注意我在评论中附加的“:)”!而且我没有说命名空间不好。
我不禁觉得using namespace是邪恶的,就像goto是邪恶的一样。两者都有有效的用途,但 1000 次中有 999 次会被错误地使用。所以,是的,在源代码中使用 using namespace 您不会污染其他包含的命名空间,整洁。但它仍然不能保护您免受 using namespace Foo + using namespace Bar 引起的 "fun" 与您调用 (implicit Foo::) baz(xyz) 并突然代码中断(没有相关更改)只是因为 Bar::baz() 被添加到某处,恰好是一个更好的匹配(因此现在被调用)
@AdmiralAdama 是的,当然需要包含该标头-但这可以间接完成(标头包括其他标头等)。所以这个错误是比较罕见的......但是 它发生时它可能非常讨厌(你调用的函数发生变化),很难检测到(通过添加一个函数来触发 somewhere< /i>,所以它发布的风险很高)而且很难追踪(代码“看起来”100% 正确)。我在 answer over at software engineering 中给出了更详细的示例
P
Peter Mortensen

不应在全局范围内使用 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...)要好,因为它更短并且能够处理用户定义的浮点类型(通过 argument-dependent lookup (ADL))。


@Billy:没有其他方法可以支持调用 userlib::cos(userlib::superint)。每个功能都有用处。
@Zan:当然有。 using std::cos;using std::sin 等。但问题是任何设计良好的 userlib 都将在其自己的命名空间中拥有它们的 sincos,因此这对您确实没有帮助。 (除非在这个模板之前有一个 using namespace userlib 并且它和 using namespace std 一样糟糕 - 并且范围没有限制。)此外,我见过的唯一这样的函数是 swap,并且在在这种情况下,我建议只创建 std::swap 的模板特化并避免整个问题。
@BillyONeal:template<typename T> void swap(MyContainer<T>&, MyContainer<T>&)(没有函数模板部分专业化(FTPS),所以有时您需要求助于重载。
@BillyONEal:您的(7 次赞成!)评论是错误的——您描述的情况正是 ADL 旨在涵盖的内容。简而言之,如果 x 有一个或多个“关联命名空间”(例如,如果它在 namespace userlib 中定义),那么任何看起来像 cos(x) 的函数调用都会另外查看这些命名空间—— 无需事先任何using namespace userlib;。 Zan Lynx 是对的(C++ 名称查找是拜占庭式的......)
而不是 using namespace std;,我更喜欢 using std::sin; using std::cos; using std::exp;。您可以获得相同的好处,而没有将 std::* 倾倒到函数中的任何风险。
p
prosach

不要全局使用

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

你把你正在编程的命名空间弄得乱七八糟。

当您使用许多 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 的实现以找到潜在的重载并选择它。


“std::swap 的实现被改变以找到潜在的重载并选择它。” - 什么?您确定吗?尽管在 C++11 中提供自定义 swap 确实不再那么重要,因为 std::swap 本身更灵活(使用移动语义)。但是 std::swap 会自动选择您自己的自定义交换,这对我来说绝对是新事物(而且我不太相信)。
@ChristianRau 我想是的,是的。我在某处读到了这个。我们可以随时问Howard,他应该知道。我现在是 diggingdigging...
即使在交换的情况下,更清晰(谢天谢地更常见)的习惯用法是写 using std::swap; 而不是 using namespace std;。更具体的习语具有更少的副作用,因此使代码更易于维护。
最后一句话是错的。在 C++11 中,Std Swap Two Step 被正式祝福为调用 swap正确方式,并且标准中的许多其他地方都被更改为这样调用 swap(注意为如上所述,using std::swap 是正确的方式,而不是 using namespace std)。但是 std::swap 本身被强调没有更改为找到其他一些 swap 并使用它。如果调用 std::swap,则使用 std::swap
不过,在本地键入 using std::swap 可能更明智,以减少本地命名空间,同时创建自记录代码。您很少对整个 std 命名空间感兴趣,因此只需挑选出您感兴趣的部分。
s
sth

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

如果所有标准的东西都在它自己的命名空间中,那么您不必担心与您的代码或其他库的名称冲突。


+1 更不用说distance了。在实际可行的情况下,我仍然更喜欢非限定名称,因为这增加了我的可读性。另外,我认为我们通常不会在口头演讲中对事物进行限定,并且愿意花时间解决可能的歧义,这意味着能够理解一个人在没有限定条件的情况下谈论的内容是有价值的,并将其应用于源代码意味着它的结构使得即使没有资格也很清楚它的全部内容。
不过,公平地说,如果您不包括 <iomanip>,您就不会有其中的大部分。不过,好点。
@einpoklum 您通常不必包含 <iomanip> 即可获得这些。对于前 gcc.godbolt.org/z/Kqx9q1 的 GCC 中的所有人来说,包括 <iostream> 就足够了
很确定您只需要 <iomanip> 来获取带有参数的操纵器,例如 setw
P
Peter Mortensen

另一个原因是惊喜。

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


你在开玩笑吗?我真的说不出来。如果不是,那么我个人会认为这是正常的“cout”,除非您不信任该代码,否则这将是一种 BEYOND MAJOR 代码气味,IMO。 ...如果您不信任该代码,那么您为什么首先使用它?请注意,我不是说“信任一切!!”但是,如果您正在处理一些来自 GitHub 的知名库或其他东西,这似乎也有点牵强。
@BrentRittenhouse cout 是一个不好的例子,因为每个人都承认它。但想象一下金融应用程序中的 future。是否是在指定日期买卖某物的合同?不,不是。如果代码是 std::future,你就不会那么容易混淆了。
@BrentRittenhouse 可能是一个不好的例子,至少有四个不同的库有 cout。可能是“它是标准库吗?libstdc++?stl?还有别的吗?”不,不是每个人都知道 std::cout,至少在本质上,我们收到的 7 名新员工中有 6 名不知道。因为教育课程不使用教育课程。我必须赶走printfs。或 debugs() - 来自 Qt。
真的吗?它几乎在很多关于 C++ 的书籍的第一章的第一个示例中,如果有的话,它(使用插入运算符)是一些新成员知道的唯一 C++。
@mckenzm 我可能会把它放在一本书或讲义中以减少混乱,但不会放在代码中
A
Alexander Poluektov

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

有经验的程序员也尽量避免在其源文件中完全限定名称。造成这种情况的一个次要原因是,除非有充分的理由,否则在更少的代码就足够的情况下编写更多的代码并不优雅。造成这种情况的一个主要原因是关闭参数相关查找 (ADL)。

这些好的理由是什么?有时程序员明确想要关闭 ADL,有时他们想要消除歧义。

所以以下是可以的:

函数实现中的函数级 using-directives 和 using-declarations 源文件中的源文件级 using-declarations(有时) 源文件级 using-directives


P
Peter Mortensen

我同意它不应该在全球范围内使用,但在本地使用它并不是那么邪恶,就像在 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 等使用声明声明的名称)优先于使用指令(using namespace Her_lib)在另一个范围内可访问的名称。


有趣的是,大多数其他答案如何忘记仅使用大括号 {..} 来定义命名空间的范围
P
Peter Mortensen

我也认为这是一种不好的做法。为什么?就在一天,我认为命名空间的功能是划分东西,所以我不应该把所有东西都扔进一个全局包中来破坏它。

但是,如果我经常使用 'cout' 和 'cin',我会在 .cpp 文件中写入:using std::cout; using std::cin;(永远不要在头文件中,因为它与 #include 一起传播)。我认为没有一个理智的人会命名流 coutcin。 ;)


这是一个本地 using 声明,与 using 指令完全不同。
A
Azeem

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

现在拿一百万行代码库,它不是特别大,并且您正在寻找一个错误,这意味着您知道这百万行中有一行没有完成它应该做的事情。 cout << 1; 可以读取名为 coutstatic int,将其向左移动一位,然后丢弃结果。寻找错误,我必须检查。你能看出我真的很喜欢看std::cout吗?

如果您是一名教师,并且从来不需要编写和维护任何代码为生,那么这似乎是一个非常好的主意。我喜欢在哪里看到代码(1)我知道它做了什么;并且,(2)我相信写它的人知道它的作用。


你怎么知道“std::cout << 1”没有读取 std 命名空间中名为 cout 的静态 int 将其移一并丢弃结果?另外你怎么知道“<<”是做什么的;)??? ...似乎这个答案不是避免“使用”的好数据点。
如果有人将 std::cout 重新定义为整数,那么您的问题不是技术问题,而是社会问题——有人为您解决了问题。 (您可能还应该检查所有标题中的#define true false 等内容)
当我看到 cout 时,我知道它总是 std::cout。如果我错了,这是编写此代码的人的问题,而不是我:)
P
Preet Sangha

这一切都与管理复杂性有关。使用命名空间会拉入你不想要的东西,因此可能会使调试变得更加困难(我说可能)。到处使用 std:: 更难阅读(更多文本等等)。

课程用马 - 以您最好的方式和感觉能力管理您的复杂性。


“使用命名空间会引入你不想要的东西,因此可能会使调试变得更加困难(我说可能)。”使用命名空间不会“引入”任何东西。调试不受影响。
这取决于你如何定义 pull things in。在上面的上下文中,使用它意味着 std:: 命名空间中的所有内容都被认为是在范围内。任何标识符都可能来自该命名空间,因此您在阅读代码时必须考虑到这一点。如果您仅在需要的地方引用具有名称空间的东西,它就会产生一种根本不存在的歧义。任何减少读者认知负担的事情(例如代码生命的绝大部分时间)都是一件好事,相反,任何增加它的事情都是一件坏事。因此,我在最后的免责声明。
在这种情况下使用“拉入”会产生错误的印象——它给人的印象是额外的命名空间声明将包含在程序中,不管你的意思是什么。我同意你所说的关于认知负荷的说法。
P
Peter Mortensen

考虑

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


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

class stringstream {  // Uh oh
};

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

这并不可怕,但您可以通过不在头文件或全局命名空间中使用它来避免头疼。在非常有限的范围内这样做可能没问题,但我从来没有遇到过输入额外的五个字符来阐明我的函数来自哪里的问题。


肯定在标头中,但是如果 using namespace std 仅存在于实现文件中怎么办?
K
Kevin

一个具体的例子来澄清这个问题。假设您有两个库 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,而不是执行“某事”,而是执行“完全不同的事”。没有编译器警告或任何东西。您的程序只是默默地开始做一些与以前完全不同的事情。

当您使用命名空间时,您会面临这样的情况,这就是人们不喜欢使用命名空间的原因。命名空间中的事物越多,冲突的风险就越大,因此人们可能会比使用命名空间 std(由于该命名空间中的事物数量)比其他命名空间更不舒服。

最终,这是可写性与可靠性/可维护性之间的权衡。可读性也可能是一个因素,但我可以看到这两种方式的论点。通常我会说可靠性和可维护性更重要,但在这种情况下,您将不断为相当罕见的可靠性/可维护性影响支付可写成本。 “最佳”权衡将决定您的项目和优先事项。


第二种情况为我敲定了交易。再次没有命名空间。不能在引擎盖下检测到如此细微的功能变化。
解决该问题的方法是允许命名空间成员使用版本进行标记,并有一种方法可以让 using 指令指定它应该引入使用旧版本号标记的成员,而不是那些被标记的成员与较新的。如果在程序员编写 using 指令时,库的最新版本是 147,则程序在 using 指令中包含该版本号,并且以后添加的任何函数都用更高的数字标记,代码指定版本 147 将继续以与以往相同的方式工作。
P
Peter Mortensen

您需要能够阅读与您有不同风格和最佳实践意见的人编写的代码。如果你只使用 cout,没有人会感到困惑。但是,当您有很多名称空间飞来飞去并且您看到这个类并且您不确定它的作用时,让名称空间显式充当某种注释。乍一看,“哦,这是一个文件系统操作”或“那是在做网络工作”。


P
Peter Mortensen

同时使用多个命名空间显然会导致灾难,但在我看来,使用 JUST 命名空间 std 和仅命名空间 std 并不是什么大问题,因为重新定义只能通过您自己的代码进行......

因此,只需将它们视为保留名称,如“int”或“class”,就是这样。

人们应该停止对它如此肛门。你的老师一直都是对的。只需使用一个命名空间;这就是首先使用命名空间的重点。您不应该同时使用多个。除非是你自己的。所以再一次,重新定义不会发生。


创建冲突并不难 - minendless 等短字符串出现在 std:: 命名空间中。但更重要的是,既然 std:: 中有数千个符号,读者知道他们可能不知道的新符号来自哪里很有用。
存在 std 命名空间是因为人们,无论是您、您的同事还是编写您使用的中间件的人,并不总是明智地将函数放在命名空间内。因此,您可以导入所有 std:: 而不是其他任何东西,同时仍然调用 std::min 和其他人的遗留 ::min() 之间的冲突,从它在 std 之前的时间开始。
P
Peter Mortensen

我同意这里的其他人的观点,但我想解决关于可读性的问题——你可以通过简单地在文件、函数或类声明的顶部使用 typedefs 来避免所有这些。

我通常在我的类声明中使用它,因为类中的方法倾向于处理相似的数据类型(成员),而 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。
P
Peter Mortensen

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

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


非常有趣的是,这个答案是基于 Bjarne Stroustrup 获得 -2 的任何其他人的指导...男孩 Bjarne 在将这个特性引入 C++ 时一定是一个贫穷且缺乏经验的程序员
@nyholku:见 this
N
Nithin

using namespace std 因计数不明确而引发编译错误的示例,这也是算法库中的函数。

#include <iostream>
#include <algorithm>

using namespace std;

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

::count——问题已解决。通常你会从 std 命名空间中获得比其他地方更多的东西,因此保留 using 命名空间指令可能会节省你的打字时间。
这里真正的问题是 C++ 仍然有无命名空间的全局变量。这个,以及“this”隐含在方法中的事实,导致了很多错误和问题,我什至无法计算它们,即使使用正确的“count”变量也是如此。 ;)
S
Swiss Frank

这是逐案的。我们希望将软件在其生命周期内的“总拥有成本”降至最低。声明“使用命名空间标准”有一些成本,但不使用它也有易读性成本。

人们正确地指出,在使用它时,当标准库引入新的符号和定义时,您的代码将停止编译,您可能会被迫重命名变量。然而,从长远来看,这可能是好的,因为如果您将关键字用于某些令人惊讶的目的,未来的维护者会暂时感到困惑或分心。

你不想要有一个叫做vector的模板,比如说,它不是其他人都知道的vector。并且因此在 C++ 库中引入的新定义的数量足够少,可能根本不会出现。进行这种更改成本的,但成本并不高,并且通过不将 std 符号名称用于其他目的而获得的清晰度抵消了。

考虑到类、变量和函数的数量,在每一个上都声明 std:: 可能会使您的代码混乱 50%,并使您更难理解。可以在一屏代码上采用的算法或方法中的步骤现在需要来回滚动才能遵循。这是一个真正的成本。可以说,这可能不是一个高成本,但否认它甚至存在的人是缺乏经验、教条主义或完全错误的。

我会提供以下规则:

std 与所有其他库不同。它是每个人基本上都需要知道的一个库,在我看来,最好将其视为语言的一部分。一般来说,即使没有其他库,使用命名空间 std 也是一个很好的例子。永远不要通过将它放在头文件中来强制决定编译单元(.cpp 文件)的作者。始终将决定权交给编译单元作者。即使在一个决定在任何地方使用 using namespace std 的项目中,也可能会罚款一些最好作为该规则的例外处理的模块。尽管命名空间功能允许您拥有许多具有相同符号定义的模块,但这样做会让人感到困惑。尽可能保持名称不同。即使不使用命名空间功能,如果您有一个名为 foo 的类并且 std 引入了一个名为 foo 的类,那么长期重命名您的类可能会更好。使用命名空间的另一种方法是通过为命名空间符号添加前缀来手动添加它们。我有两个已经使用了几十年的库,实际上都是从 C 库开始的,其中每个符号都以“AK”或“SCWin”为前缀。一般来说,这就像避免“使用”结构,但你不写双冒号。 AK::foo() 是 AKFoo()。它使代码更密集 5-10% 并且更少冗长,唯一的缺点是如果您必须使用两个具有相同前缀的此类库,您将遇到大麻烦。请注意,X Window 库在这方面非常出色,只是它们忘记了使用一些#defines:TRUE 和 FALSE 应该是 XTRUE 和 XFALSE,这与同样使用 TRUE 和 FALSE 的 Sybase 或 Oracle 建立了命名空间冲突具有不同的价值观! (在数据库的情况下是 ASCII 0 和 1!)这样做的一个特殊优点是它无缝地应用于预处理器定义,而 C++ using/namespace 系统不处理它们。这样做的一个很好的好处是,它提供了从成为项目的一部分到最终成为图书馆的有机坡度。在我的一个大型应用程序中,所有窗口类都以 Win 为前缀,所有信号处理模块都以 Mod 为前缀,等等。这些中的任何一个都几乎没有被重用的机会,因此将每个组都变成一个库并没有实际的好处,但是在几秒钟内,项目如何分解为子项目就很明显了。


最后,谢谢!节省您编写的每个代码的时间与“也许”至少使用 std 库修复遗留代码的时间。
P
Peter Mortensen

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

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

如本页所述:使用命名空间 std 的语句通常被认为是不好的做法。此语句的替代方法是在每次声明类型时使用作用域运算符 (::) 指定标识符所属的命名空间。并看到这样的观点:当您大量使用命名空间并确定不会发生冲突时,在源文件中使用“使用命名空间 std”没有问题。

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

如本页所述:尽管该语句使我们免于键入 std:: 每当我们希望访问定义在 std 命名空间中的类或类型时,它会将整个 std 命名空间导入程序的当前命名空间。让我们举几个例子来理解为什么这可能不是一件好事......现在在开发的后期阶段,我们希望使用另一个版本的 cout,它是在一些名为“foo”的库中自定义实现的(例如) ... 请注意如何存在歧义,cout 指向哪个库?编译器可能会检测到这一点而不编译程序。在最坏的情况下,程序可能仍然编译但调用了错误的函数,因为我们从未指定标识符属于哪个命名空间。


P
Peter Mortensen

我同意其他人的观点——它要求名称冲突、含糊不清,而事实是它不那么明确。虽然我可以看到 using 的使用,但我个人的偏好是限制它。我也会强烈考虑其他人指出的内容:

如果您想查找一个可能是相当常见的名称的函数名称,但您只想在 std 命名空间中找到它(或相反 - 您想更改所有 的调用在命名空间 std、命名空间 X、...) 中,那么您打算怎么做呢?

您可以编写一个程序来完成它,但是花时间在您的项目本身上工作而不是编写一个程序来维护您的项目不是更好吗?

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

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

除此之外,您可以拥有命名空间别名。这是一个可能没有被提及的有用的示例。我使用 C++11 标准,特别是 libstdc++。好吧,它没有完整的 std::regex 支持。当然,它可以编译,但它会抛出一个异常,因为它是程序员端的错误。但它缺乏实施。

所以这就是我解决它的方法。安装 Boost 的正则表达式,并将其链接。然后,我执行以下操作,以便当 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;
}

我不会争论这是否是一个坏主意。然而,我会争辩说,它为我的项目保持清洁,同时使其具体:确实,我必须使用 Boost,但我使用它就像 libstdc++ 最终会拥有它一样。是的,开始您自己的项目并从一开始就使用标准(...)对于帮助维护、开发和与项目相关的一切有很大帮助!

只是为了澄清一些事情:我实际上并不认为在 STL 中故意使用类名/任何内容来代替是一个好主意。字符串对我来说是个例外(忽略第一个、上面或第二个,如果必须的话,双关语),因为我不喜欢“字符串”的想法。

事实上,我仍然非常偏向 C 和偏向 C++。保留细节,我工作的大部分内容更适合 C不那么封闭,不那么傲慢,更容易接受。)。但是, 有用的是一些人已经提出的建议:我确实使用列表(它相当通用,不是吗?),并排序(同样的事情)命名两个,如果我要做using namespace std;,因此我更喜欢具体、可控并且知道如果我打算将其作为标准用途,那么我将不得不指定它。简单地说:不允许假设。

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


Extending the std namespace is undefined behaviour,因此永远不应该这样做。
P
Peter Mortensen

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

例如,如果我输入 using namespace std;using namespace otherlib;,然后只输入 cout(恰好两者都有),而不是 std::cout(或 'otherlib::cout'),您可能会使用错误的,并且得到错误。使用 std::cout 更加有效和高效。


D
Dr. Watson

我不认为在所有情况下都一定是不好的做法,但是在使用它时需要小心。如果您正在编写一个库,您可能应该将范围解析运算符与命名空间一起使用,以防止您的库与其他库发生冲突。对于应用程序级代码,我看不出有什么问题。


A
August Karlstrom

对于不合格的导入标识符,您需要像 grep 这样的外部搜索工具来找出声明标识符的位置。这使得推理程序正确性变得更加困难。


a
adn.911

这是一种不好的做法,通常称为全局命名空间污染。当多个命名空间具有相同的函数名称和签名时,可能会出现问题,那么编译器将无法决定调用哪个命名空间,而当您使用 std::cout 等函数调用指定命名空间时,这一切都可以避免.希望这可以帮助。 :)


P
Peter Mortensen

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

我反过来说:为什么有些人认为输入五个额外的字符很麻烦?

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


这不仅仅是 5 个额外的字符;每次引用标准库中的任何对象类型时,它都会增加 5 个额外的字符。如果您经常使用标准库,那么会经常使用。因此,在一个体面的程序中更现实地是数千个额外的字符。据推测,“使用”指令已添加到语言中,以便可以使用它......
它不是每次都多出 5 个字符,而是 5 个字符,可能需要单击几下鼠标来下拉菜单并在您选择的编辑器中进行查找和替换。
可读性。 cout << hex << setw(4) << i << endl;std::cout << std::hex << std::setw(4) << i << std::endl; 更容易阅读
更糟糕的是:与 map<string,pair<string,string>> 相比,std::map<std::string,std::pair<std::string,std::string>> 很糟糕。
一个好的做法是无论如何都要对你的 STL 容器进行 typedef,所以 std:: 真的没关系。而 C++11 为我们带来了 auto 关键字,这让例如使用迭代器时事情变得更加容易。
N
Noneyo Getit

为了回答你的问题,我实际上是这样看待的:很多程序员(不是全部)调用命名空间 std。因此,人们应该养成不使用与命名空间 std 中的内容冲突或使用相同名称的东西的习惯。这是理所当然的,但与严格来说可以提出的可能连贯的单词和假名的数量相比,这并不算多。

我的意思是真的......说“不要依赖这个存在”只是让你依赖它不存在。您会经常遇到借用代码片段并不断修复它们的问题。只需将您的用户定义和借用的东西保持在应有的有限范围内,并且非常谨慎地使用全局变量(老实说,全局变量几乎总是为了“立即编译,稍后理智”的最后手段)。真的,我认为这是您老师的坏建议,因为使用 std 将适用于“cout”和“std::cout”,但不使用 std 仅适用于“std::cout”。您不会总是有幸编写自己的所有代码。

注意:在您真正了解编译器的工作原理之前,不要过多地关注效率问题。有了一点编码经验,您不必对它们了解太多,然后您就会意识到它们能够将好的代码概括为简单的东西。每一点都像你用 C 写整个东西一样简单。好的代码只有它需要的复杂程度。


鉴于有多少人似乎不知道有用的标准库函数(例如,从 <algorithm> 重新发明事物),想象同样的人可以可靠地避免这些标识符似乎有点牵强。查看您自己的代码并告诉我您从来没有一个名为 count 的变量或函数。或 distance,或 logdestroylaunchvisitbetasamplemessagesclamperasecopymodulusleft 等。更不用说所有尚未出现在 std 中的标识符,它们会在 C++35 出现时破坏您的代码......