Using std Namespace
对于在std名称空间中使用"using"似乎有不同的观点。
有些人说使用"EDOCX1"(0),另一些人说不使用,而是使用前缀std函数,这些函数将与"EDOCX1"(1)一起使用,而另一些人说使用类似这样的函数:
1 2 3 4 5 | using std::string; using std::cout; using std::cin; using std::endl; using std::vector; |
用于所有要使用的标准函数。
每种方法的优缺点是什么?
大多数C++用户都非常乐意阅读EDCOX1,8,EDCOX1,9,等等。事实上,看到原始EDOCX1的10度,这让我想知道这是EDCOX1还是9,还是一个不同的用户定义的EDCOX1,10?
我一直反对使用
下面是
不需要的名称冲突的典型示例如下所示。想象一下,你是个初学者,不了解
1 2 3 4 5 6 7 8 9 | #include using namespace std; int count = 0; int increment() { return ++count; // error, identifier count is ambiguous } |
这个错误通常很长而且不友好,因为
但这是正常的,因为
1 2 3 4 5 6 7 8 | #include using namespace std; int increment() { static int count = 0; return ++count; } |
也许有点奇怪,这还可以。导入到声明性作用域中的标识符出现在公共命名空间中,该命名空间包含定义它们的位置和导入它们的位置。换句话说,
1 2 3 4 5 6 7 8 | #include int increment() { using namespace std; static int count = 0; return ++count; } |
由于类似的原因,
1 2 3 4 5 6 7 8 9 | #include int count = 0; int increment() { using namespace std; return ++count; // error ambiguous } |
排除基础知识(必须在所有STL对象/函数之前添加std::infont,如果没有"使用命名空间std",则冲突的可能性更小)
同样值得注意的是,你不应该
1 | using namespace std |
在头文件中,因为它可以传播到包含该头文件的所有文件,即使它们不想使用该名称空间。
在某些情况下,使用诸如
1 | using std::swap |
就像有专门版本的swap一样,编译器将使用它,否则它将回到std::swap上。
如果调用std::swap,则始终使用基本版本,它不会调用优化版本(如果存在)。
首先,一些术语:
- 使用声明:
using std::vector; 。 - 使用指令:
using namespace std; 。
我认为使用using指令是可以的,只要它们不在头文件的全局范围内使用。所以拥有
1 | using namespace std; |
在您的.cpp文件中并不是一个真正的问题,如果结果是这样的话,它完全在您的控制之下(如果需要的话,它甚至可以被限定到特定的块范围内)。我看不出有什么特别的理由用大量的
同样,如果您可以在
记住有时必须使用using声明也是一件好事。请参阅Scott Meyers的"项目25:考虑支持非投掷交换"从有效的C++,第三版。为了让一个通用的、模板化的函数对参数化类型使用"最佳"交换方法,您需要使用using声明和依赖于参数的查找(也称为adl或koenig查找):
1 2 3 4 5 6 7 8 9 10 11 12 | template< typename T > void foo( T& x, T& y) { using std::swap; // makes std::swap available in this function // do stuff... swap( x, y); // will use a T-specific swap() if it exists, // otherwise will use std::swap<T>() // ... } |
我认为我们应该看看各种语言的常见习惯用法,这些语言大量使用名称空间。例如,Java和C语言在很大程度上使用命名空间(可以说是MeSeo而不是C++)。在这些语言中使用名称空间中最常见的方法是将名称空间集中到当前作用域中,并使用相当于using指令的方法。这并没有引起广泛的问题,而且很少的问题是在"异常"的基础上处理的,通过完全限定的名称或别名处理所涉及的名称——就像C++中可以完成的一样。
Habor萨特和Andrei Alexandrescu在"第59项:不要在头文件中使用命名空间,或者在他们的书、C++编码标准之前包含"命名空间":101条规则、指南和最佳实践:
In short: You can and should use namespace using declarations and directives liberally in your implementation files after
#include directives and feel good about it. Despite repeated assertions to the contrary, namespace using declarations and directives are not evil and they do not defeat the purpose of namespaces. Rather, they are what make namespaces usable.
在"C++编程语言,第三版"中,Stroupstrup经常被引用为"不要污染全局命名空间"。他确实这么说(c.14[15]),但提到了c.10.1章,他说:
A using-declaration adds a name to a
local scope. A using-directive does
not; it simply renders names
accessible in the scope in which they
were declared. For example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 namespaceX {
int i , j , k ;
}
int k ;
void f1()
{
int i = 0 ;
using namespaceX ; // make names from X accessible
i++; // local i
j++; // X::j
k++; // error: X::k or global k ?
::k ++; // the global k
X::k ++; // X’s k
}
void f2()
{
int i = 0 ;
using X::i ; // error: i declared twice in f2()
using X::j ;
using X::k ; // hides global k
i++;
j++; // X::j
k++; // X::k
}A locally declared name (declared
either by an ordinary declaration or
by a using-declaration) hides nonlocal
declarations of the same name, and any
illegal overloadings of the name are
detected at the point of declaration.Note the ambiguity error for
k++ in
f1() . Global names are not given
preference over names from namespaces
made accessible in the global scope.
This provides significant protection
against accidental name clashes, and –
importantly – ensures that there are
no advantages to be gained from
polluting the global namespace.When libraries declaring many names
are made accessible through
using-directives, it is a significant
advantage that clashes of unused names
are not considered errors....
I hope to see a radical decrease in
the use of global names in new
programs using namespaces compared to
traditional C and C++ programs. The
rules for namespaces were specifically
crafted to give no advantages to a
‘‘lazy’’ user of global names over
someone who takes care not to pollute
the global scope.
如何与"懒惰的全局名称用户"有相同的优势?通过利用using指令,它可以安全地使名称空间中的名称对当前作用域可用。
请注意,
不要在头文件的全局范围内使用命名空间。这可能导致冲突,并且冲突出现的文件负责人对原因没有控制权。
在实现文件中,选择要少得多。
放置一个using-namespace-std会带来该名称空间中的所有符号。这可能会很麻烦,因为几乎没有人知道所有的符号(因此在实践中不可能有冲突的政策),而不提到将要添加的符号。C++标准允许头从其他头添加符号(C一个不允许)。在控制案例中简化写作,在实践中仍能取得较好的效果。如果出现错误,则在出现问题的文件中检测到错误。
使用std::name;的优点是编写简单,不存在导入未知符号的风险。代价是您必须显式地导入所有需要的符号。
明确限定添加了一点混乱,但我认为这是一些实践中较少的麻烦。
在我的项目中,我对所有名称都使用明确的限定条件,我接受使用std::name,我反对反对使用名称空间std(我们有一个lisp解释器,它有自己的列表类型,所以冲突是必然的)。
对于其他名称空间,还必须考虑使用的命名约定。我知道一个项目使用名称空间(用于版本控制)和名称前缀。那么做一个
在某些情况下,您希望导入符号。std::swap是最常见的情况:导入std::swap,然后使用swap unqualified。依赖于参数的查找将在类型的命名空间中找到适当的交换(如果有),如果没有,则返回到标准模板。
编辑:
在评论中,迈克尔·伯尔怀疑冲突是否发生在现实世界中。这是一个真实的实例。我们有一种扩展语言,它是Lisp方言。我们的解释器有一个include文件,lisp.h包含
1 | typedef struct list {} list; |
我们必须集成并修改一些代码(我将其命名为"引擎"),这些代码如下所示:
1 2 3 4 5 | #include <list> ... using std::list; ... void foo(list const&) {} |
所以我们修改如下:
1 2 3 4 5 6 7 | #include <list> #include"module.h" ... using std::list; ... void foo(list const&) {} |
很好。一切正常。几个月后,"module.h"被修改为包含"list.h"。测试通过了。"模块"没有以影响其ABI的方式进行修改,因此可以在不重新编译用户的情况下使用"引擎"库。集成测试正常。发布了新的"模块"。引擎的下一次编译在其代码未被修改时中断。
两个
1 | using std::string; |
和
1 | using namespace std; |
向全局命名空间添加一些符号(一个或多个)。在头文件中,向全局名称空间添加符号是不应该做的事情。您无法控制谁将包含您的头,有许多包含其他头的头(以及包含包含头的头等等…)。
在实现(.cpp)文件中,这取决于您(只有记住在所有include指令之后才这样做)。您可以只破坏这个特定文件中的代码,这样更容易管理和找出名称冲突的原因。如果您喜欢在标识符之前使用std::(或任何其他前缀,项目中可能有许多名称空间),这是可以的。如果您想将使用的标识符添加到全局名称空间中,可以。如果您想让整个名称空间出现在您的头上:—),这取决于您自己。虽然效果仅限于单个编译单元,但可以接受。
对我来说,我更喜欢尽可能使用
1 | std::list<int> iList; |
我讨厌写:
1 2 3 4 | for(std::list<int>::iterator i = iList.begin(); i != iList.end(); i++) { // } |
希望,用C++ 0x我会写这个:
1 2 3 4 | for(auto i = iList.begin(); i != iList.end(); i++) { // } |
如果名称空间很长,
1 2 3 4 5 6 7 8 9 10 | namespace dir = boost::filesystem; dir::directory_iterator file("e:/boost"); dir::directory_iterator end; for( ; file != end; file++) { if(dir::is_directory(*file)) std::cout << *file << std::endl; } |
如果代码中没有与std和其他库发生名称冲突的风险,则可以使用:
1 | using namespace std; |
但是,如果您希望准确了解代码对文档的依赖性,或者存在名称冲突的风险,请使用另一种方式:
1 2 | using std::string; using std::cout; |
第三个解决方案是,不要使用这些解决方案,在代码中每次使用之前都要编写std::以提高安全性,但是,代码中可能会有一些负担…
在头中的名称空间范围内,不应该是
如果您认为必须这样做,请添加本地using声明,如
考虑到这一点,在十年前的一个项目中,我们决定用它们的完整名称空间名称显式地限定所有标识符。起初看起来很尴尬的事情在两周内就变成了例行公事。现在,在整个公司的所有项目中,没有人再使用指令或声明了。(有一个例外,见下文)看了十年后的代码(几个MLOC),我觉得我们做出了正确的决定。
我发现,通常情况下,那些反对取缔
注意:唯一的例外是
名称空间包含代码,以防止混淆和污染函数签名。
下面是一个完整的、有文档记录的正确名称空间使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | #include <iostream> #include <cmath> // Uses ::log, which would be the log() here if it were not in a namespace, see https://stackoverflow.com/questions/11892976/why-is-my-log-in-the-std-namespace // Silently overrides std::log //double log(double d) { return 420; } namespace uniquename { using namespace std; // So we don't have to waste space on std:: when not needed. double log(double d) { return 42; } int main() { cout <<"Our log:" << log(4.2) << endl; cout <<"Standard log:" << std::log(4.2); return 0; } } // Global wrapper for our contained code. int main() { return uniquename::main(); } |
输出:
1 2 | Our log: 42 Standard log: 1.43508 |
手动指定要在EDOCX1中导入的内容可以防止这种情况发生,但可能会导致在文件开头出现一长串使用列表,有些开发人员会发现这很难看;)!
就个人而言,我更喜欢在每次使用函数时指定名称空间,除非名称空间太长,在这种情况下,我在文件的开头使用了一些。
编辑:正如另一个答案所指出的,您不应该将
伊迪丝2:感谢查尔斯的评论,更正了我的回答。
有几种方法可以解决这个问题。
第一:像你做的那样使用。
第二:做
第三:使用
第四:不要使用
只要您使用的IDE不够灵活,无法显示或隐藏所需的准确信息,此讨论就将继续进行。
这是因为您希望代码的外观取决于手头的任务。
在创建源代码时,我更喜欢确切地看到我使用的是哪个类:是
在设计控制流时,我甚至对变量的类型不感兴趣,但我希望重点关注
所以我的建议是:
根据代码的受众和工具的功能,选择最容易阅读或提供最多信息的方式。
非常类似于Java,您可以在其中使用Java或UTI.*,也可以单独选择每个类,这取决于样式。请注意,您不希望在文件/宽范围的开头使用一个
例如,为什么不呢
1 2 3 4 | typedef std::vector<int> ints_t; ints_t ints1; .... ints_t ints2; |
而不是笨拙的
1 2 3 | std::vector<int> ints1; ... std::vector<int> ints2; |
我发现它更易读,它是我的编码标准。
甚至可以使用它为读者提供一些语义信息。例如,考虑函数原型
1 | void getHistorgram(std::vector<unsigned int>&, std::vector<unsigned int>&); |
哪一个是回报值?
换一个怎么样
1 2 3 4 | typedef std::vector<unsigned int> values_t; typedef std::vector<unsigned int> histogram_t; ... void getHistogram(values_t&, histogram_t&); |
What are the pros and cons of each
离开std::的唯一原因是,理论上,您可以自己重新实现所有stl函数。然后您的函数可以从使用std::vector切换到my::vector,而无需更改代码。