c++的一个特性是能够创建未命名(匿名)的名称空间,如下所示:

namespace {
    int cannotAccessOutsideThisFile() { ... }
} // namespace

您可能会认为这样的特性毫无用处——因为不能指定名称空间的名称,因此不可能从外部访问其中的任何内容。但是,这些未命名的名称空间可以在创建它们的文件中访问,就像对它们有一个隐式的using子句一样。

我的问题是,为什么或者什么时候这比使用静态函数更可取?或者它们本质上是做同一件事的两种方式?


当前回答

我刚才在看你的问题时才了解到这个特点,我只能猜测。这似乎比文件级静态变量提供了几个优势:

匿名名称空间可以嵌套在另一个名称空间中,提供多级保护,使符号无法逃脱。 可以在同一个源文件中放置多个匿名名称空间,从而在同一个文件中创建不同的静态级作用域。

我想知道是否有人在实际代码中使用过匿名名称空间。

其他回答

编译以下代码可以看出匿名名称空间和静态函数之间特定于编译器的区别。

#include <iostream>

namespace
{
    void unreferenced()
    {
        std::cout << "Unreferenced";
    }

    void referenced()
    {
        std::cout << "Referenced";
    }
}

static void static_unreferenced()
{
    std::cout << "Unreferenced";
}

static void static_referenced()
{
    std::cout << "Referenced";
}

int main()
{
    referenced();
    static_referenced();
    return 0;
}

使用VS 2017编译这段代码(指定4级警告标志/W4来启用警告C4505:未引用的本地函数已被删除)和gcc 4.9使用-Wunused-function或-Wall标志显示VS 2017只会对未使用的静态函数产生警告。GCC 4.9及更高版本,以及clang 3.3及更高版本,将对名称空间中未引用的函数产生警告,并对未使用的静态函数产生警告。

gcc 4.9和MSVC 2017的现场演示

在第7.3.1.1节第2段中,c++标准如下:

static关键字的用法是 类中声明对象时不支持 命名空间范围,即未命名的命名空间 提供了一个更好的选择。

Static仅适用于对象、函数和匿名联合的名称,不适用于类型声明。

编辑:

反对使用static关键字(影响翻译单元中变量声明的可见性)的决定已被推翻(ref)。在这种情况下,使用静态名称空间或未命名名称空间本质上是做完全相同的事情的两种方式。更多讨论请参见这个SO问题。

未命名的名称空间仍然具有允许您定义翻译单元局部类型的优势。请参阅这个SO问题了解更多细节。

感谢迈克·珀西让我注意到这一点。

区别在于被破坏的标识符的名称(_ZN12_GLOBAL__N_11bE vs _ZL1b,这并不重要,但它们都被组装为符号表中的局部符号(缺少.global asm指令)。

#include<iostream>
namespace {
   int a = 3;
}

static int b = 4;
int c = 5;

int main (){
    std::cout << a << b << c;
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4
_ZL1b:
        .long   4
        .globl  c
        .align 4
        .type   c, @object
        .size   c, 4
c:
        .long   5
        .text

对于嵌套的匿名命名空间:

namespace {
   namespace {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_112_GLOBAL__N_11aE:
        .long   3

翻译单元中所有第一级匿名命名空间相互组合,翻译单元中所有第二级嵌套匿名命名空间相互组合

还可以在匿名名称空间中拥有嵌套名称空间或嵌套内联名称空间

namespace {
   namespace A {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11A1aE, @object
        .size   _ZN12_GLOBAL__N_11A1aE, 4
_ZN12_GLOBAL__N_11A1aE:
        .long   3

which for the record demangles as:
        .data
        .align 4
        .type   (anonymous namespace)::A::a, @object
        .size   (anonymous namespace)::A::a, 4
(anonymous namespace)::A::a:
        .long   3

//inline has the same output

您还可以使用匿名的内联名称空间,但据我所知,匿名名称空间上的内联效果为0

inline namespace {
   inline namespace {
       int a = 3;
    }
}

_ZL1b: _Z表示这是一个错误的标识符。L表示它是通过静态的局部符号。1是标识符b的长度,然后是标识符b

_ZN12_GLOBAL__N_11aE _Z表示这是一个错误的标识符。N表示这是一个命名空间12是匿名命名空间名称_GLOBAL__N_1的长度,然后是匿名命名空间名称_GLOBAL__N_1,然后1是标识符a的长度,a是标识符a, E关闭命名空间中的标识符。

_ZN12_GLOBAL__N_11A1aE与上面相同,除了其中有另一个命名空间(1A)称为A,前缀为A的长度为1。匿名命名空间的名称都是_GLOBAL__N_1

根据我的经验,我只想指出,虽然这是c++将以前的静态函数放入匿名名称空间的方式,但旧的编译器有时会在这方面遇到问题。我目前正在使用一些用于目标平台的编译器,更现代的Linux编译器可以很好地将函数放置到匿名名称空间中。

但是在Solaris上运行的旧编译器有时会接受它,有时会将其标记为错误,直到未来未指定的版本发布。我担心的不是这个错误,而是它在接受错误时可能会做什么。因此,在全面现代化之前,我们仍然使用静态(通常是类作用域)函数,我们更喜欢匿名名称空间。

我最近开始在代码中用匿名名称空间替换静态关键字,但很快就遇到了一个问题,即名称空间中的变量在调试器中不再可用。我使用的是VC60,所以我不知道其他调试器是否没有问题。我的解决方法是定义一个“模块”命名空间,在那里我给它我的cpp文件的名字。

例如,在我的xmlutil_cpp文件中,我定义了一个名称空间XmlUtil_I{…}用于我的所有模块变量和函数。这样我就可以在调试器中应用XmlUtil_I::资格来访问变量。在本例中,_I将其与公共名称空间区分开来,例如我可能希望在其他地方使用的XmlUtil。

我认为,与真正的匿名方法相比,这种方法的潜在缺点是,有人可能会在其他模块中使用名称空间限定符,从而违反所需的静态作用域。我不知道这是否是一个主要的问题。