在命名空间内部定义未命名的命名空间

28

我被要求修改的一些代码看起来很像这样:

namespace XXX {

namespace {

// some stuff

} // end of unnamed

// Some stuff within the scope of XXX

} // end of XXX

我很难看出将未命名命名空间嵌套在另一个命名空间中的优点(如果有的话),因此我正在考虑将其更改为:

namespace {

// some stuff

} // end of unnamed

namespace XXX {

// Some stuff within the scope of XXX

} // end of XXX

非常感谢您提出的任何意见。

5个回答

23

它确实有实际的好处。 未命名的命名空间将其内部的名称从不同的翻译单元中隐藏。

上述代码之所以有效,仅因为foo的定义在相同的翻译单元中。

假设main()和foo()的定义在不同的翻译单元中。 它会编译,因为主文件包含了声明的头文件。但是它不会链接,因为逻辑上不存在X::(未命名的命名空间)::foo这样的东西。


2
这适用于两个示例。因此,您不是在描述差异,也不是一个优于另一个的好处。 - Lightness Races in Orbit
@LightnessRacesinOrbit:区别/好处在于你的调用方式。使用提议的(顶部)方式,你将调用 XXX::some_function(); ,而使用你的提议,则需要像这样调用 some_function(); 。没有 XXX:: 的调用可能更容易出错,因为你可能会意外地从全局范围调用外部函数。 - Grim Fandango
@GrimFandango 我知道这一点 - 但回答应该解释清楚。我的评论是对答案的批评,而不是请求帮助。 - Lightness Races in Orbit

10

从全局视角来看,两种方法都没有太多好处:从其他翻译单元的角度来看,这两种方法的结果相同:匿名命名空间是不可见的(或无法引用)。

从同一翻译单元的角度来看,有所不同:你定义一个顶层命名空间意味着你减少了在其他地方声明的命名空间冲突的可能性,最常见的就是全局命名空间(无命名空间函数,想象一下从ISO C继承的任何内容,比如stdio.h或其他内容)。

例如,如果你在该翻译单元中导入的全局头文件具有“无命名空间”的abort()函数,并且你在你的翻译单元中声明了一个命名空间 { abort() { ...} },那么就会产生歧义,例如gcc将抛出编译错误:

error: call of overloaded ‘abort()’ is ambiguous

现在,如果你在命名空间内部命名一个匿名命名空间,则会产生以下影响:
a) 对于在该命名空间内声明的函数,不存在歧义,因为它具有优先权:
namespace a { namespace { abort() {...} } }

如果您有一个像a::whatever()这样的函数,并且它引用了abort(),那么它会在自己的命名空间中解析,因为它具有优先权。

b) 对于a::abort(),您不会有全局链接,因为它不存在于翻译单元之外,与顶级的namespace { abort(); }相同,但没有上述潜在冲突。

而在"b"中存在着区别: 它并不等同于只是namespace a { abort(); },因为它没有全局链接,所以您可以在另一个翻译单元中重新定义它而没有冲突。祝你尝试链接两个定义namespace a { abort() { ... } }的翻译单元好运...

因此,您将获得您想要的结果:

namespace a { // you have a named space, so you don't have conflicts with the nameless one
  namespace { // but you have local visibility and linkage
    whatever(); // for this
  }
}

简而言之:这两种方法有相似之处,但也有区别。有人可能会认为这并不是非常有用,但作为一种风格,它可以预防与全局命名空间的冲突。有人可能会说,既然这些问题在编译时就能被捕获(希望至少在签名完全匹配时),那么为什么要费心呢?但如果您的项目是一个可移植的库,您的头文件可能会受到环境头文件导入的影响而被污染,那么这是一个有用的概念,否则您的用户将不得不为其系统修补您的库,或者您需要在各个地方使用 #ifdef。
我经常在 ISO/ANSI C 99 上编程,偶尔需要做一些这样的事情:
#include <headerA.h>
#define symbol symbolB
#include <headerB.h>
// or some crap alike. And I have linker problems with above.

由于两个头文件(例如来自不同库)都污染了命名空间,我无法简单地修补别人的库。

C++ 命名空间可以解决这个问题,除非有人不使用它,因此必须采取措施来防止(对于旧代码来说不是一个选项)或对抗它。


10
好的,原来的X::<anonymous>::foo()实际上可以被视为X::foo()。我很惊讶。
所以说,实际上几乎没有什么实质性的好处。但可能会有一些语义或文档方面的影响。

原始回答

嗯,这取决于“东西”的具体内容。
现有代码允许在X中编写“私有”其他代码,该代码也在X中,但无法从X外部访问:
#include <iostream>

namespace X {
   namespace {
      void foo() { std::cout << "lol\n"; }
   }
   
   void bar() { foo(); }
}

int main()
{
   X::bar();
   // X::foo();  // can't do this directly  [edit: turns out we can!]
}
  • 输出:lol\n

您提出的方法使得“私有内容”对整个翻译单元都可用:

#include <iostream>

namespace {
   void foo() { std::cout << "lol\n"; }
}

namespace X {
   void bar() { foo(); }
}

int main()
{
   X::bar();
   foo();     // works
}
  • 输出:lol\nlol\n

4
因此,实际上几乎没有什么实际好处。但是,确实存在实际好处。来自未命名命名空间的名称无法从其他翻译单元中访问,即使它们有外部链接性,除非它们被声明为“static”或“const”。 - Nawaz
2
@abhinav:是的,因为你是从同一个翻译(编译)单元访问它。 - Nawaz
这个答案很久以前就提供了,而且在我看来是错误的。在你的第一个例子中,你假设main()X::foo()驻留在同一个TU中,所以当然在main()中调用X::foo()是可以工作的。另外,实际上有一个巨大的好处:你仍然可以把东西放在命名空间中,但是提供静态链接。不同的TU将无法访问X::foo(),而foo仍然(逻辑上)属于namespace X。第二个例子将foo()放入全局范围,这与A::foo()非常不同,可能会导致名称冲突等问题(更不用说失去语义)。 - andreee
@andreee:“第二个示例将foo()放入全局范围内。” 不,它并没有这样做。 - Lightness Races in Orbit
@andreee 不,那只是重新措辞同样错误的说法。该符号位于未命名的命名空间中,而不是全局命名空间中。/ 除此之外,据我所知,您正在说的好处是您的TU本地、未命名命名空间foo仍然“逻辑上”在X内部,我不会对此争论,但这不是一个重要的好处,我在我的答案第二段中已经说过了! - Lightness Races in Orbit
显示剩余8条评论

3
使用命名空间内部的未命名命名空间的好处之一是防止参数依赖查找(ADL)。 实际上,如果在同一个命名空间中定义类型和函数,可能会遇到不良影响。 以下示例说明了该问题:
假设我们有一个简单的头文件如下,在其中为我们的自定义类型mytype定义通用函数myfunction,并将其放在我们的命名空间mynamespace中。
//header.h
namespace mynamespace {
    struct mytype {};

    static void myfunction(mytype){}
}

现在,在另一个名为test.cpp的文件中,我们想要为特定目的(如打印调试信息、执行额外操作、国际化等)覆盖myfunction,而不改变程序的其余部分。以下代码不会与我们的头文件冲突,因为它是一个不同的命名空间,即全局命名空间。
//test.cpp
#include "header.h"

static void myfunction(mynamespace::mytype){}

int main(){
    mynamespace::mytype val = {};
    //error: call of overloaded 'myfunction(mynamespace::mytype&)' is ambiguous
    myfunction(val);
}

令人惊讶的是,上面对 myfunction 的调用存在二义性并且无法编译通过,这是由于ADL的原因。为了解决这个问题,你可以通过使用匿名命名空间来防止ADL。

//header.h
namespace mynamespace {
    struct mytype {};

    //OK prevents ADL
    namespace {
        static void myfunction(mytype){}
    }
}

1

一个简单的好处是,从命名空间内的匿名命名空间中,可以直接访问命名空间的内容:

namespace foo {
    class Bar;

    namespace {
        void Baz() {
            Bar bar;  // no need to specify the foo namespace 
            …
        }
    }

    …
}

改为:

class foo::Bar;

namespace {
    void Baz() {
        foo::Bar bar;
        …
    }
}

namespace foo {
    …
}

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