在命名空间中使用声明的范围

59

在C++头文件中,以下面的方式在命名空间中使用using声明是否安全(且正确):

#include <boost/numeric/ublas/vector.hpp>
namespace MyNamespace {
    using boost::numeric::ublas::vector;
    vector MyFunc(vector in);
}

也就是说,"using boost::numeric::ublas::vector" 是否正确地包含在 MyNamespace 块中,还是会污染任何包含此头文件的文件的命名空间?


3
你所说的“任何文件的命名空间”具体是什么意思?从using声明的声明点开始,它将在任何翻译单元中“污染”MyNamespace命名空间。 - CB Bailey
为什么不使用 typedef 来表示单个符号? - Matthieu M.
1
@Matthieu:因为boost::numeric::ublas::vector是一个模板。我之前使用标准的“模板typedef”解决方法(https://dev59.com/8XVD5IYBdhLWcg3wTJrF),但想简化一下事情。 - Brett Ryland
2
啊!在C++0x中,你有办法给模板取别名……虽然你需要重新声明所有想要使用的参数,但除此之外,你似乎陷入了困境。 - Matthieu M.
5个回答

45
不,这是不安全的——它不会污染另一个命名空间,但因为其他原因而危险: using指令将导入任何当前可见的指定名称的内容到您使用它的命名空间中。虽然您的using只对MyNamespace的用户可见,但来自“外部”的其他东西将对您的using声明可见。
那么当在头文件中使用时,这样做有何危险?因为它将导入在声明点可见的内容,具体行为取决于声明前包含的头文件的顺序(可能有不同的东西从boost::numeric::ublas::vector可见)。由于您实际上无法控制在头文件之前包含哪个头文件(也不应该这样做!头文件应该是自给自足的!),这可能导致非常奇怪的问题,在一个编译单元中找到一个东西,在下一个编译单元中找到另一个东西。
作为一个经验法则,using声明应该仅在.cpp文件的所有包含文件之后使用。Sutter和Alexandrescu的书《C++编码标准》(第59条)中还有一条关于这个确切问题的内容。这里是一句引语:

但这是一个常见的陷阱:很多人认为在命名空间级别发出的using声明是安全的。他们不是。它们至少同样危险,而且以一种更微妙和隐蔽的方式。

即使您使用的名称很可能不存在其他任何地方(在这里可能是这种情况),事情也可能变得很糟糕:在头文件中,所有声明都应该是完全限定的。这很痛苦,但否则会发生奇怪的事情。

还可以参考迁移到名称空间使用声明和名称空间别名名称空间命名的示例以及深入描述的问题。


4
请注意,我正在使用using boost::numeric::ublas::vector,而不是using namespace boost::numeric::ublas,因为我不想导入整个boost::numeric::ublas名称空间。另外,由于这是在namespace MyNamespace {}块内声明的,如果有人编写了像using namespace std; using namespace MyNamespace;这样的代码,才会使vector具有二义性。 - Brett Ryland
4
你的意思是什么会有歧义?在MyNamespace中,使用 声明 引入的 vector 会隐藏任何由使用 指令 引入全局命名空间中可见的 vector。这肯定是有意为之的吧? - CB Bailey
3
这个观点仍然是有效的。不过,我必须说我还没有看出对于类型而言问题在哪里。 - CB Bailey
3
总之,如果我在头文件的命名空间块中使用using ::boost::numeric::ublas::vector,是否可以避免头文件中可能出现的歧义?如果后面某个人在.cpp文件中调用了using namespace std; using namespace MyNamespace,这将导致歧义,但是using namespace std; using namespace boost::numeric::ublas;也会有同样的问题。因此,这并不是一个严重的问题,对吗? - Brett Ryland
7
我一直在寻找方法,以使语法更易读,而不需要过多的 std:: 前缀。我很想在定义自己的类之前,在我的命名空间中使用 using ::std::vector,以便代码更易于阅读。我知道 using namespace 是有风险的,并且我能理解对函数进行 using 声明可能出现的问题。但据我所知,仅存在类型冲突的可能性,如果“其他人”在我的命名空间中定义了一个同名类型,那么这是真的会成为如此大的问题吗?难道没有人使用这种模式吗? - thomthom
显示剩余7条评论

15

using声明是一种声明,如其名称所示。所有声明都作用于封闭块(7.2)中,本例中为命名空间MyNamespace。它在该命名空间之外不可见。


1
谢谢,我认为这就是我想要的。基本上,我希望该命名空间中的所有向量都是 boost::numeric::ublas::vector,以便任何包含此头文件并使用 using namespace MyNamespace; 声明的 .cpp 文件都使用此向量而不是 std::vector。但其他情况则不然。 - Brett Ryland
2
@Brett:如果他们有一个using namespace std;,你就会遇到名称冲突。我总是更喜欢使用完全限定的名称。你可以为命名空间创建一个短别名。 - Björn Pollex
3
“它在该命名空间之外将不可见。” 这句话是正确的,但反过来就不是:命名空间之外的内容将对using指令可见,可能会改变你和其他人代码的含义。 - ltjax

5

这是安全的,但它会污染MyNamespace命名空间。因此,包含该头文件的任何文件都将在MyNamespace中具有函数/类。


3
但是原帖仅仅导入了一个类型,而不是整个命名空间。这是否算是污染? - thomthom

4
总之,不可以在头文件中使用using-declarations,即使在命名空间内部也不行,有两个原因。此外,在非头文件中使用命名空间中的using-declarations容易出错或者没有意义(见结尾)。在头文件中使用using-declarations是不允许的,因为:
  1. 它们会将名称引入命名空间中,这会影响到包含该头文件的所有文件
  2. 它们只引入已经被看到的名称声明,这意味着行为取决于包含的顺序!

在您的示例中,这意味着:

  1. MyNamespace中,vector现在可能解析为任何包含此头文件的文件中的boost::numeric::ublas::vector:这会“污染”MyNamespace命名空间。
  2. 导入了哪些boost::numeric::ublas::vector声明取决于此using-declaration之前出现的声明,这取决于包含的顺序在包含此头文件的文件及其所有包含文件中的预处理后的翻译单元中的声明顺序

根据您在2011年5月30日11:51的评论,您实际上想要行为1,但由于问题2,这是不可行的。您可以通过有一个单独的头文件,在所有其他头文件之后被包含,并在其他头文件中完全限定名称来获得所需的行为。然而,这是脆弱的,因此不建议使用,最好只在转换到命名空间时使用:

//--- file myheader.hpp ---
#include <boost/numeric/ublas/vector.hpp>
namespace MyNamespace {
    ::boost::numeric::ublas::vector MyFunc(::boost::numeric::ublas::vector in);
}

//--- file myproject_last.hpp ---
namespace MyNamespace {
    using ::boost::numeric::ublas::vector;
}

//--- file myproject.cpp ---
#include "myheader.hpp"
// ...other includes
#include "myproject_last.hpp"

请参阅GotW#53:迁移到命名空间,了解详细信息、此解决方法和建议:“头文件中不应出现命名空间使用声明”。为了避免问题1,可以在使用声明周围添加未命名的命名空间(以防止这些名称可见),然后在未命名的命名空间之外再添加一个命名空间(使所需的名称本身可见),但仍然受到问题2的影响并且使头文件变得难看。
//--- file myheader.hpp ---
#include <boost/numeric/ublas/vector.hpp>
namespace MyNamespace {
    namespace {
        using ::boost::numeric::ublas::vector;
        vector MyFunc(vector in);
    }
    using MyFunc; // MyNamespace::(unique)::MyFunc > MyNamespace::MyFunc
}

由于这些问题,你应该仅在非头文件(.cc/.cpp)中使用using声明:这不会影响其他文件,因此避免了问题1;并且已经包含了所有的头文件,所以避免了问题2。在这种情况下,是否将它们放在命名空间中是个人口味问题,因为它们不会影响其他文件;在使用声明本身上始终使用完全限定名称(绝对名称,以::开头)是最安全的。
最简单的方法是将所有的using声明放在文件顶部,在包含(include)语句之后,但在任何命名空间之外:这是安全的、明确的、易于阅读的,并且允许在整个文件中使用这些声明的名称。一些常见的偏差包括:
  1. Using-declaration within a function (or struct or class or nested block): fine. This minimizes scope and is just a matter of taste: using-declaration is close to use (legibility win), but they are now scattered throughout the file (legibility loss).
  2. Using-declaration with a relative name within a (named) namespace: error-prone. This is more concise and adds some clarity (related names used in the namespace to which they relate), but is potentially ambiguous (just like includes with relative paths), and is safer to avoid:

    using ::foo::bar;
    namespace foo { ... }
    
    namespace foo {
        // Implicitly ::foo:bar, could be ::bar, or ::other::foo::bar.
        using bar;
    }
    
  3. Using-declaration with an absolute name within a named namespace: pointless. This introduces the name only into the namespace, but you shouldn't care, since you shouldn't be including the .cc/.cpp file:

    namespace foo {
        using ::bar;
    }
    
  4. Using-declaration within an unnamed namespace: pointless, slightly dangerous. For example, if you have a function in an unnamed namespace, say an implementation detail, then you can have a using-declaration for its return type or param types. This introduces the name into just that namespace (so can't be referenced from other files), but again, you shouldn't care, since you shouldn't be including the .cc/.cpp file (unnamed namespaces are especially for avoid name clashes at link time, which isn't applicable here: it's just a compile-time alias). Worse, it introduces ambiguity if that name already does exist!


1

它不会污染其他命名空间,但肯定会污染MyNamespace命名空间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接