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

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

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

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


当前回答

在一种边缘情况下,静电会产生令人惊讶的效果(至少对我来说是这样)。c++ 03标准在14.6.4.2/1中声明:

For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2) except that: For the part of the lookup using unqualified name lookup (3.4.1), only function declarations with external linkage from the template definition context are found. For the part of the lookup using associated namespaces (3.4.2), only function declarations with external linkage found in either the template definition context or the template instantiation context are found. ...

下面的代码将调用foo(void*),而不是你可能期望的foo(S const &)。

template <typename T>
int b1 (T const & t)
{
  foo(t);
}

namespace NS
{
  namespace
  {
    struct S
    {
    public:
      operator void * () const;
    };

    void foo (void*);
    static void foo (S const &);   // Not considered 14.6.4.2(b1)
  }

}

void b2()
{
  NS::S s;
  b1 (s);
}

就其本身而言,这可能不是什么大问题,但它确实强调了对于完全兼容的c++编译器(即支持导出的编译器),static关键字仍然具有其他任何方式都无法提供的功能。

// bar.h
export template <typename T>
int b1 (T const & t);

// bar.cc
#include "bar.h"
template <typename T>
int b1 (T const & t)
{
  foo(t);
}

// foo.cc
#include "bar.h"
namespace NS
{
  namespace
  {
    struct S
    {
    };

    void foo (S const & s);  // Will be found by different TU 'bar.cc'
  }
}

void b2()
{
  NS::S s;
  b1 (s);
}

确保未命名名称空间中的函数不会在使用ADL的模板中找到的唯一方法是将其设置为静态。

为现代c++更新

从c++ '11开始,未命名命名空间的成员隐式具有内部链接(3.5/4):

未命名的名称空间或在未命名的名称空间中直接或间接声明的名称空间具有内部链接。

但与此同时,14.6.4.2/1被更新,删除了链接(这来自c++ '14):

对于后缀表达式是依赖名称的函数调用,候选函数使用 通常的查找规则(3.4.1,3.4.2),除了: 对于使用非限定名称查找的部分(3.4.1),只找到模板定义上下文中的函数声明。 对于使用关联名称空间的查找部分(3.4.2),只会找到在模板定义上下文或模板实例化上下文中找到的函数声明。

结果是静态名称空间成员和未命名名称空间成员之间的这种特殊区别不再存在。

其他回答

在一种边缘情况下,静电会产生令人惊讶的效果(至少对我来说是这样)。c++ 03标准在14.6.4.2/1中声明:

For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2) except that: For the part of the lookup using unqualified name lookup (3.4.1), only function declarations with external linkage from the template definition context are found. For the part of the lookup using associated namespaces (3.4.2), only function declarations with external linkage found in either the template definition context or the template instantiation context are found. ...

下面的代码将调用foo(void*),而不是你可能期望的foo(S const &)。

template <typename T>
int b1 (T const & t)
{
  foo(t);
}

namespace NS
{
  namespace
  {
    struct S
    {
    public:
      operator void * () const;
    };

    void foo (void*);
    static void foo (S const &);   // Not considered 14.6.4.2(b1)
  }

}

void b2()
{
  NS::S s;
  b1 (s);
}

就其本身而言,这可能不是什么大问题,但它确实强调了对于完全兼容的c++编译器(即支持导出的编译器),static关键字仍然具有其他任何方式都无法提供的功能。

// bar.h
export template <typename T>
int b1 (T const & t);

// bar.cc
#include "bar.h"
template <typename T>
int b1 (T const & t)
{
  foo(t);
}

// foo.cc
#include "bar.h"
namespace NS
{
  namespace
  {
    struct S
    {
    };

    void foo (S const & s);  // Will be found by different TU 'bar.cc'
  }
}

void b2()
{
  NS::S s;
  b1 (s);
}

确保未命名名称空间中的函数不会在使用ADL的模板中找到的唯一方法是将其设置为静态。

为现代c++更新

从c++ '11开始,未命名命名空间的成员隐式具有内部链接(3.5/4):

未命名的名称空间或在未命名的名称空间中直接或间接声明的名称空间具有内部链接。

但与此同时,14.6.4.2/1被更新,删除了链接(这来自c++ '14):

对于后缀表达式是依赖名称的函数调用,候选函数使用 通常的查找规则(3.4.1,3.4.2),除了: 对于使用非限定名称查找的部分(3.4.1),只找到模板定义上下文中的函数声明。 对于使用关联名称空间的查找部分(3.4.2),只会找到在模板定义上下文或模板实例化上下文中找到的函数声明。

结果是静态名称空间成员和未命名名称空间成员之间的这种特殊区别不再存在。

将方法放在匿名名称空间中可以防止您意外地违反One Definition Rule,允许您不必担心将您的helper方法命名为与您可能链接的其他方法相同的方法。

而且,正如luke所指出的,标准更喜欢匿名名称空间而不是静态成员。

区别在于被破坏的标识符的名称(_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

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

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

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

c++ 98标准不赞成为此目的使用static关键字。静态的问题在于它不适用于类型定义。它也是一个重载关键字,在不同的上下文中以不同的方式使用,因此未命名的名称空间简化了一些事情。