未命名/匿名名称空间vs.静态函数

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

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

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

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

290737 次浏览

c++标准在7.3.1.1节未命名命名空间,第2段:

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

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

编辑:

不赞成使用static关键字(影响翻译单元中变量声明的可见性)的决定已被逆转(裁判)。在这种情况下,使用static或未命名的namespace本质上是做完全相同的事情的两种方式。更多讨论请参见 SO问题。

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

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

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

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

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

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

将方法放在匿名名称空间中可以防止您意外地违反一个定义规则,允许您永远不必担心将您的helper方法命名为您可能链接的其他方法。

而且,正如路加福音所指出的,标准首选匿名名称空间而不是静态成员。

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

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

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

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

对于依赖于模板形参的函数调用,如果函数名是unqualified-id而不是模板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的模板中找到的唯一方法是将其设置为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),只会找到在模板定义上下文或模板实例化上下文中找到的函数声明。

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

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

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

此外,如果在变量上使用static关键字,例如:

namespace {
static int flag;
}

在映射文件中不会看到它

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

#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的现场演示 . 0

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

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

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

  • 静态函数在C或c++中的表现非常相似,而无名名称空间显然只适用于c++。无名命名空间也增加了额外的缩进级别,我不喜欢这样:)

所以,我很高兴看到静态函数的使用不再被弃用

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

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

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

_ZL1b: _Z表示这是一个错误的标识符。L表示它是从staticstatic的局部符号。1是标识符b和标识符b的长度

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

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