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

scope of using declaration within a namespace

提问人:Brett Ryland 提问时间:5/30/2011 最后编辑:EnlicoBrett Ryland 更新时间:9/15/2021 访问量:31058

问:

在 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 块中,或者这会污染包含此标头的任何文件的命名空间?

C++ 范围 命名空间 头文件 使用

评论

3赞 CB Bailey 5/30/2011
“任何文件的命名空间”到底是什么意思?它将“污染”任何翻译单元中的命名空间,从使用声明开始。MyNamespace
0赞 Matthieu M. 5/30/2011
对于单个符号...你为什么不用一个?typedef
1赞 Brett Ryland 5/30/2011
@Matthieu:因为是模板。我以前使用标准的“模板类型定义”解决方法(stackoverflow.com/questions/26151/...),但想简化一下。boost::numeric::ublas::vector
2赞 Matthieu M. 5/30/2011
啊!在 C++0x 中,您可以通过设置模板别名的方法...虽然你需要重新声明你希望使用的所有参数,但否则你就会被卡住,我猜。

答:

5赞 BЈовић 5/30/2011 #1

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

评论

3赞 thomthom 12/15/2013
但是 OP 只导入了单个类型,而不是整个命名空间。这是污染吗?
15赞 Björn Pollex 5/30/2011 #2

顾名思义,using 声明就是声明。所有声明的范围都限定为封闭块 (7.2),在本例中为 namespace 。它将在该命名空间之外不可见。MyNamespace

评论

1赞 Brett Ryland 5/30/2011
谢谢,我认为这就是我想要得到的。基本上,我希望此命名空间中的所有向量都是 s,以便任何包含此标头并使用声明的 .cpp 文件都使用此向量而不是 .但不是其他。boost::numeric::ublas::vectorusing namespace MyNamespace;std::vector
2赞 Björn Pollex 5/30/2011
@Brett:如果他们有名字冲突,你就会有名字冲突。我总是更喜欢完全限定的名字。您始终可以为命名空间创建短别名。using namespace std;
3赞 ltjax 5/30/2011
“它将在该命名空间之外不可见”——虽然这是正确的,但反过来却不是:外部的东西将对 using 指令可见,这可能会改变你和其他人代码的含义。
1赞 Puppy 5/30/2011 #3

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

45赞 ltjax 5/30/2011 #4

不,它不安全 - 它不会污染另一个命名空间,但由于其他原因,它是危险的:

指令会将您指定的名称当前可见的任何内容导入到使用它的命名空间中。虽然 your 将仅对 的用户可见,但来自“外部”的其他内容将对您的声明可见。usingusingMyNamespaceusing

那么,当在标题中使用时,这有什么危险呢?因为它将导入在声明点可见的内容,所以确切的行为将取决于您在声明之前包含的标头的顺序(可能有不同的内容可见)。由于您无法真正控制在标头之前包含哪些标头(也不应该!标头应该是自给自足的!),这可能会导致非常奇怪的问题,即您的函数将在一个编译单元中找到一件事,而在下一个编译单元中找到另一件事。boost::numeric::ublas::vector

根据经验,只有在 .cpp 文件中包含所有内容,才应使用声明。在Sutter和Alexandrescu合著的《C++编码标准》(Risk 59)一书中,也有关于这个问题的文章。这是一句话:using

但这里有一个常见的陷阱:许多人认为使用在命名空间级别 (...) 发出的声明是安全的。他们不是。它们至少同样危险,而且是以一种更微妙、更阴险的方式。

即使你的名字不太可能在其他任何地方都不存在(就像这里的情况一样),事情也会变得丑陋:在标头中,所有声明都应该是完全限定的。这是痛苦的,但除此之外,奇怪的事情可能会发生。using

另请参阅迁移到命名空间、使用声明和命名空间别名和命名空间命名,了解示例和深入描述的问题。

评论

4赞 Brett Ryland 5/30/2011
请注意,我使用的不是,因为我不想导入整个命名空间。此外,由于这是在块内声明的,因此 vector 不应该只在有人编写类似 ?using boost::numeric::ublas::vectorusing namespace boost::numeric::ublasboost::numeric::ublasnamespace MyNamespace { }using namespace std; using namespace MyNamespace;
4赞 CB Bailey 5/30/2011
@ltjax:你到底是什么意思会模棱两可?在 using 声明中引入的任何向量都将隐藏由 using 指令引入的全局命名空间中可见的任何向量。这肯定是用心吗?MyNamespacevector
3赞 CB Bailey 5/30/2011
“这一点仍然有效。”也许吧,尽管我必须说我还没有看到类型的问题是什么。
3赞 Brett Ryland 5/30/2011
@ltjax:总而言之,如果我在头文件中的命名空间块内使用,这会避免头文件中可能出现的歧义吗?如果有人稍后调用.cpp文件,这将导致歧义,但...所以没有太大的问题,是吗?using ::boost::numeric::ublas::vectorusing namespace std; using namespace MyNamespaceusing namespace std; using namespace boost::numeric::ublas;
7赞 thomthom 12/16/2013
我也一直在寻找方法,使语法更具可读性,而没有太多的前缀。在定义类之前,我真的很想在我自己的命名空间中使用,以便代码更容易阅读。我知道这是有风险的,如果为函数声明,我可以理解这个问题。但据我了解,类型的唯一问题是,如果“其他人”应该在我的命名空间中定义一个同名的类型,则可能会发生冲突。这真的是一个没有人使用这种模式的大问题吗?std::using ::std::vectorusing namespaceusing
4赞 Nils von Barth 5/22/2016 #5

总而言之,,即使在命名空间中,在标头中使用声明也是不行的,原因有 2 个。此外,在非标头的命名空间中使用声明容易出错或毫无意义(见结尾)。在标头中使用声明是不行的,因为:

  1. 它们在命名空间中引入了一个名称,这会影响包含标头的所有文件
  2. 它们只引入已经看到的名称的声明,这意味着行为取决于包含的顺序!

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

  1. 对于包含此标头的任何文件,现在 中 可能会解析为 :它会“污染”命名空间。MyNamespacevectorboost::numeric::ublas::vectorMyNamespace
  2. 导入哪些声明取决于在此 using-declaration 之前显示的声明,这取决于包含此标头的文件中 includes 的顺序,以及它的所有 include(正确地说,是预处理后转换单元中声明的顺序)。boost::numeric::ublas::vector

根据您在 11 年 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:迁移到命名空间:“使用声明的命名空间不应出现在头文件中。

可以通过在 using-declaration 周围添加一个未命名的命名空间(以防止这些名称可见)然后在未命名的命名空间之外添加另一个命名空间(使所需名称本身可见)来避免问题 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-declarations:这不会影响其他文件,因此可以避免问题 1;并且所有标头都已包含,因此避免了问题 2。在这种情况下,是否将它们放在命名空间中是一个品味问题,因为它们不会影响其他文件;最安全的做法是在 using 声明本身中始终使用完全限定的名称(绝对,以 开头)。::

最简单的方法是将所有 using 声明放在文件顶部,在 includes 之后,但在任何命名空间之外:这是安全的、明确的、易于阅读的,并且允许在整个文件中使用名称。一些常见的偏差:

  1. 在函数(或结构、类或嵌套块)中使用声明:很好。这最小化了范围,只是一个品味问题:using-declaration 接近使用(易读性获胜),但它们现在分散在整个文件中(易读性损失)。
  2. 在(命名的)命名空间中具有相对名称的 Using-declaration:错误倾向。这更简洁,并增加了一些清晰度(在它们相关的命名空间中使用的相关名称),但可能不明确(就像相对路径的包含一样),并且避免使用会更安全:

    using ::foo::bar;
    namespace foo { ... }
    
    namespace foo {
        // Implicitly ::foo:bar, could be ::bar, or ::other::foo::bar.
        using bar;
    }
    
  3. 在命名命名空间中使用绝对名称的声明:无意义。这只会将名称引入命名空间,但您不应该关心,因为您不应该包含 .cc/.cpp 文件:

    namespace foo {
        using ::bar;
    }
    
  4. 在未命名的命名空间中使用声明:毫无意义,有点危险。例如,如果你在一个未命名的命名空间中有一个函数,比如一个实现细节,那么你可以有一个 using 声明来表示它的返回类型或参数类型。这会将名称引入该命名空间(因此无法从其他文件引用),但同样,您不应该关心,因为您不应该包含 .cc/.cpp 文件(未命名的命名空间特别用于避免链接时的名称冲突,这在这里不适用:它只是一个编译时别名)。更糟糕的是,如果该名称已经存在,则会产生歧义!