我听说过使用名称空间std;这是不好的做法,我应该直接使用std::cout和std::cin。这是为什么?声明与std命名空间中的某个变量同名的变量是否有风险?
当前回答
是的,名称空间很重要。在我的项目中,我需要将一个var声明导入到源代码中,但在编译时,它与另一个第三方库冲突。
最后,我不得不用其他方法来解决这个问题,并使代码变得不那么清晰。
其他回答
有经验的程序员使用任何解决他们问题的方法,避免任何产生新问题的方法。出于这个确切的原因,他们避免使用头文件级别的指令。
经验丰富的程序员也尽量避免在源文件中使用完全限定的名称。这样做的一个次要原因是,除非有充分的理由,否则在代码较少的情况下编写更多的代码是不优雅的。一个主要原因是关闭参数依赖查找(ADL)。
这些好理由是什么?有时程序员明确希望关闭ADL,有时他们希望消除歧义。
因此,以下内容是可以的:
函数级使用指令并在函数的实现中使用声明使用源文件内声明的源文件级别(有时)使用指令的源文件级别
不要全局使用它
只有在全球范围内使用时,它才被认为是“坏的”。因为:
你把正在编程的命名空间弄得乱七八糟。当您使用许多名称空间xyz;时,读者将很难看到特定标识符的来源;。无论对你的源代码的其他读者来说是什么,对最经常阅读源代码的读者来说更是如此:你自己。一两年后回来看看。。。如果您只讨论使用名称空间std;你可能不知道你抓取的所有内容——当你添加另一个#include或移动到一个新的C++修订版时,你可能会遇到你不知道的名称冲突。
您可以在本地使用
继续,在本地(几乎)自由使用它。当然,这可以防止你重复std::--,重复也是不好的。
本地使用它的习惯用法
在C++03中,有一种习惯用法——样板代码——用于为类实现交换函数。有人建议您实际使用本地using命名空间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,即void std::swap(int,int)。如果实现了重载void swap(Child&,Child&),编译器将选择它。如果没有重载,编译器将使用void std::swap(Child&,Child&),并尽可能地交换这些。
对于C++11,没有理由再使用这种模式了。对std::swap的实现进行了更改,以找到一个潜在的过载并选择它。
另一个原因是惊讶。
如果我看到cout<<blah,而不是std::cout<<blah,我会想:这是什么cout?这是正常的服装吗?有什么特别的吗?
这是个案。我们希望在软件的生命周期内最大限度地降低软件的“总拥有成本”。声明“using namespace std”有一定的代价,但不使用它也有易读性的代价。
人们正确地指出,当使用它时,当标准库引入新的符号和定义时,您的代码将停止编译,您可能被迫重命名变量。然而,这可能是一个很好的长期目标,因为如果您出于某种令人惊讶的目的使用关键字,未来的维护人员将暂时感到困惑或分心。
你不希望有一个名为vector的模板,比如说,它不是其他人都知道的向量。因此,C++库中引入的新定义的数量非常少,可能根本找不到。必须进行这种更改是有代价的,但代价并不高,而且通过不将std符号名称用于其他目的而获得的清晰性来抵消。
考虑到类、变量和函数的数量,在每一个上声明std::可能会使代码出错50%,并使您更难理解。一个算法或方法中的一个步骤可以在一屏代码上执行,现在需要前后滚动才能执行。这是一个真实的成本。可以说,这可能不是一个高成本,但那些否认它存在的人是缺乏经验的、教条的,或者根本就是错误的。
我提供以下规则:
std不同于所有其他库。这是每个人基本上都需要知道的一个库,在我看来,最好将其视为语言的一部分。一般来说,使用命名空间std是一个很好的例子,即使没有其他库。永远不要通过将此using放在头中来迫使编译单元(.cpp文件)的作者做出决定。始终将决定权交给编译单元的作者。即使在一个决定在任何地方使用名称空间std的项目中,也可能会有一些模块作为该规则的例外处理。尽管名称空间功能允许您有许多定义相同符号的模块,但这样做会令人困惑。请尽可能保持名称不同。即使不使用名称空间特性,如果您有一个名为foo的类,并且std引入了一个名foo的类型,那么长期来说重命名您的类可能会更好。使用名称空间的另一种方法是通过前缀手动命名符号。我有两个库,我用了几十年,实际上都是从C库开始的,每个符号的前缀都是“AK”或“SCWin”。一般来说,这就像避免使用“using”构造,但不使用双冒号。AK::foo()改为AKFoo()。它使代码更密集5-10%,更不冗长,唯一的缺点是,如果必须使用两个具有相同前缀的这样的库,则会遇到很大的麻烦。注意,X Window库在这方面非常出色,只是它们忘记了使用一些#定义:TRUE和FALSE应该是XTRUE和XFALSE,这与Sybase或Oracle产生了命名空间冲突,它们同样使用了TRUE和FALSE,但值不同!(在数据库的情况下为ASCII 0和1!)这样做的一个特殊优点是,它似乎适用于预处理器定义,而C++使用/命名空间系统不处理它们。这样做的一个好处是,它提供了从项目的一部分到最终成为图书馆的有机坡度。在我的一个大型应用程序中,所有的窗口类都以Win为前缀,所有的信号处理模块都以Mod为前缀等等。这些模块中的任何一个都不太可能被重用,因此将每个组放入一个库中没有实际的好处,但几秒钟后就可以看出项目是如何分解为子项目的。
一个具体的例子来澄清这个问题。假设您有两个库,foo和bar,每个库都有自己的名称空间:
namespace foo {
void a(float) { /* Does something */ }
}
namespace bar {
...
}
现在假设您在自己的程序中同时使用foo和bar,如下所示:
using namespace foo;
using namespace bar;
void main() {
a(42);
}
现在一切都很好。当你运行程序时,它会“做一些事情”。但后来你更新了这个栏,让我们假设它变成了这样:
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。这是不可取的,但幸运的是它非常容易(只需在编译器标记为不明确的所有调用前面添加foo::)。
但想象一下另一种情况,酒吧改成了这样:
namespace bar {
void a(int) { /* Does something completely different */ }
}
此时,您对a(42)的调用突然绑定到bar::a而不是foo::a,并且它不做“某事”,而是做“完全不同的事情”。没有编译器警告或任何内容。你的程序只是默默地开始做一些与以前完全不同的事情。
当你使用一个名称空间时,你会面临这样的风险,这就是为什么人们不喜欢使用名称空间的原因。名称空间中的东西越多,冲突的风险就越大,因此与其他名称空间相比,人们可能更不喜欢使用名称空间std(由于该名称空间中有很多东西)。
最终,这是可写性与可靠性/可维护性之间的权衡。可读性也可能是其中的一个因素,但无论哪种方式,我都能看到这样做的理由。通常,我会说可靠性和可维护性更重要,但在这种情况下,您将不断为相当罕见的可靠性/可维护性影响支付可写性成本。“最佳”权衡将决定您的项目和优先级。