592

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

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

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

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

4

11 回答 11

384

C++ 标准在第 7.3.1.1 节未命名命名空间,第 2 段中写道:

在命名空间范围内声明对象时,不推荐使用 static 关键字,未命名命名空间提供了更好的选择。

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

编辑:

弃用关键字的这种使用static(影响翻译单元中变量声明的可见性)的决定已被撤销(ref)。在这种情况下,使用 astatic或 unnamednamespace本质上是两种做同样事情的方式。有关更多讨论,请参阅SO question。

Unnamednamespace仍然具有允许您定义翻译单元本地类型的优势。有关更多详细信息,请参阅SO 问题。

感谢Mike Percy让我注意到了这一点。

于 2008-09-30T19:06:19.613 回答
84

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

而且,正如luke所指出的,匿名命名空间比静态成员更受标准的青睐。

于 2008-09-30T23:20:49.690 回答
41

在一种极端情况下,静态具有令人惊讶的效果(至少对我来说是这样)。C++03 标准在 14.6.4.2/1 中声明:

对于依赖于模板参数的函数调用,如果函数名称是unqualified-id但不是template-id,则使用通常的查找规则(3.4.1、3.4.2)找到候选函数,但以下情况除外:

  • 对于使用非限定名称查找 (3.4.1) 的查找部分,仅找到具有来自模板定义上下文的外部链接的函数声明。
  • 对于使用关联命名空间 (3.4.2) 的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的具有外部链接的函数声明。

...

下面的代码将调用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++ 编译器(即支持 的编译器export),该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 的模板中找不到我们未命名命名空间中的函数的唯一方法是 make 它static

现代 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) 的查找部分,只能找到在模板定义上下文或模板实例化上下文中找到的函数声明。

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

于 2008-10-01T09:15:19.370 回答
12

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

例如,在我的 XmlUtil.cpp 文件中,我XmlUtil_I { ... }为所有模块变量和函数定义了一个命名空间。这样我就可以XmlUtil_I::在调试器中应用限定来访问变量。在这种情况下,它将它与我可能想在其他地方使用_I的公共命名空间区分开来。XmlUtil

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

于 2008-10-01T00:39:12.090 回答
8

根据经验,我只是注意到,虽然这是将以前的静态函数放入匿名命名空间的 C++ 方式,但较旧的编译器有时会遇到问题。我目前为我们的目标平台使用了一些编译器,更现代的 Linux 编译器可以将函数放入匿名命名空间中。

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

于 2008-10-26T20:43:26.557 回答
8

我个人更喜欢静态函数而不是无名命名空间,原因如下:

  • 仅从函数定义就可以清楚地看出,它对于编译它的翻译单元是私有的。对于无名命名空间,您可能需要滚动并搜索以查看函数是否在命名空间中。

  • 命名空间中的函数可能会被某些(较旧的)编译器视为 extern。在 VS2017 中,它们仍然是外部的。出于这个原因,即使函数位于无名命名空间中,您可能仍希望将它们标记为静态。

  • 静态函数在 C 或 C++ 中的行为非常相似,而无名命名空间显然只是 C++。无名命名空间还增加了额外的缩进级别,我不喜欢这样 :)

所以,我很高兴看到不再不推荐使用静态函数。

于 2018-04-05T18:25:46.430 回答
7

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

于 2008-09-30T19:13:35.273 回答
5

不同之处在于损坏标识符的名称(_ZN12_GLOBAL__N_11bEvs _ZL1b,这并不重要,但它们都被组装成符号表中的局部符号(没有.globalasm 指令)。

#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

翻译单元中的所有 1 级匿名命名空间相互组合,翻译单元中的所有 2 级嵌套匿名命名空间相互组合

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

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

您也可以使用匿名内联命名空间,但据我所知,inline匿名命名空间的效果为 0

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

_ZL1b:_Z表示这是一个损坏的标识符。L通过 . 表示它是一个本地符号static1是标识符的长度,b然后是标识符b

_ZN12_GLOBAL__N_11aE _Z表示这是一个损坏的标识符。N表示这是一个命名空间12是匿名命名空间名称的长度_GLOBAL__N_1,然后是匿名命名空间名称_GLOBAL__N_1,然后1是标识符的长度aa是标识符aE关闭驻留在命名空间中的标识符。

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

于 2020-06-03T15:06:48.720 回答
3

此外,如果有人像这个例子一样在变量上使用 static 关键字:

namespace {
   static int flag;
}

它不会在映射文件中看到

于 2011-12-08T19:02:23.557 回答
2

编译以下代码可以看到匿名命名空间和静态函数之间的编译器特定差异。

#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: unreferenced local function has been removed)和带有 -Wunused-function 或 -Wall 标志的 gcc 4.9 表明 VS 2017 只会产生警告未使用的静态函数。gcc 4.9 及更高版本,以及 clang 3.3 及更高版本,将对命名空间中未引用的函数产生警告,并对未使用的静态函数产生警告。

gcc 4.9 和 MSVC 2017 的现场演示

于 2017-04-18T06:16:55.587 回答
-1

刚刚在阅读您的问题时才了解此功能,我只能推测。这似乎比文件级静态变量提供了几个优点:

  • 匿名命名空间可以相互嵌套,提供多级保护,符号无法逃脱。
  • 几个匿名命​​名空间可以放在同一个源文件中,实际上在同一个文件中创建不同的静态级别范围。

我有兴趣了解是否有人在真实代码中使用过匿名名称空间。

于 2008-09-30T19:09:26.597 回答