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


507

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

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

您会认为这样的功能将毫无用处-因为您无法指定名称空间的名称,所以无法从外部访问名称空间中的任何内容。但是,这些未命名的名称空间可在创建它们的文件访问,就好像您对它们具有隐式的使用子句一样。

我的问题是,为什么或何时比使用静态函数更好?还是它们本质上是两种完全相同的方法?


13
在C ++ 11 static中,不赞成在这种情况下使用;尽管无名命名空间是一种更好的选择static,但在某些情况下,static抢救失败
legends2k 2013年

Answers:


332

C ++标准的内容在第7.3.1.1节“未命名的名称空间”的第2段中:

在命名空间范围内声明对象时,不建议使用static关键字,unnamed-namespace提供了一种更好的选择。

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

编辑:

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

未命名的命名空间仍然具有允许您定义本地翻译单元类型的优点。请参阅 SO问题以获取更多详细信息。

幸得迈克·珀西提出这个引起我的注意。


39
Head Geek询问仅针对函数使用的静态关键字。应用于名称空间范围中声明的实体的static关键字指定其内部链接。在匿名名称空间中声明的实体具有外部链接(C ++ / 3.5),但是可以保证它生活在唯一命名的范围内。未命名名称空间的匿名性有效地隐藏了其声明,使其只能从翻译单元内部访问。后者有效地以与static关键字相同的方式工作。
mloskot

5
外部联系的缺点是什么?这会影响内联吗?
亚历克斯(Alex)

17
那些说过不推荐使用static关键字的C ++设计委员会人员可能在大型的现实世界系统中从未使用过巨大的C代码...(您立即看到了static关键字,但如果匿名名称空间包含很多带有大注释的声明,则看不到它)街区。)
Calmarius 2012年

23
由于此答案作为“ c ++匿名名称空间”的最佳结果出现在Google上,因此应注意,不再反对使用static。有关更多信息,请参见stackoverflow.com/questions/4726570/…open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012
Michael Percy

2
@ErikAronesty听起来是错误的。您有可复制的示例吗?从C ++ 11开始-甚至在某些编译器之前-unnames namespace隐式具有内部链接,因此应该没有区别。通过在C ++ 11中将其作为要求,可以解决以前可能由于措辞不当而引起的任何问题。
underscore_d

73

将方法放在匿名名称空间中可防止您意外违反“ 一个定义规则”,从而使您永远不必担心将您的辅助方法命名为与您可能链接的其他方法相同。

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


2
我指的是静态独立功能(即文件作用域函数),而不是静态成员函数。静态独立功能与未命名空间中的功能非常相似,因此是个问题。
怪胎头

2
啊; 好,ODR仍然适用。编辑删除段落。
hazzen's

据我了解,静态函数的ODR在标头中定义并且此标头包含在多个翻译单元中时不起作用,对吗?在这种情况下,您会收到相同功能的多个副本
Andriy Tylychko 2011年

@Andy T:如果包含标头,您实际上看不到“多个定义”。预处理器会处理它。除非有必要研究预处理器生成的输出,否则在我看来,这是非常奇特和罕见的。还有一个好的做法是在头文件中包括“防护”,例如:“#ifndef SOME_GUARD-#define SOME_GUARD ...”,这可以防止预处理器两次包含相同的头。
Nikita Vorontsov 2014年

@NikitaVorontsov,后卫可能会阻止将相同的标头包含在相同的翻译单元中,但是它允许在不同的翻译单元中使用多个定义。这可能会导致“多个定义”链接器错误。
亚历克斯

37

在一种极端情况下,静电会产生令人惊讶的效果(至少对我而言)。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在模板中找不到我们未命名名称空间中的函数的唯一方法是使用它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)进行的查找,仅会找到在模板定义上下文或模板实例化上下文中找到的函数声明。

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


3
出口关键字不是应该冷死了吗?唯一支持“导出”的编译器是实验性的编译器,除非出乎意料,否则由于意外的副作用(除了不希望这样做),“导出”甚至不会在其他程序中实现
2008年

2
参见Herb Sutter关于该产品的文章:gotw.ca/publications/mill23-x.htm
paercebal

3
爱迪生设计集团(EDG)的前端只是实验性质的。几乎可以肯定,它是世界上最符合标准的C ++实现。英特尔C ++编译器使用EDG。
理查德·科登

1
哪些C ++功能没有“意外的副作用”?在导出的情况下,就是将从另一个TU中找到一个未命名的名称空间函数-就像您直接包含模板定义一样。如果不是这样,那就更令人惊讶了!
理查德·科登,

我认为您那里有错别字-为了NS::S工作,不必不在S里面namespace {}吗?
艾瑞克(Eric)

12

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

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

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


7
我也这样做了,但是使用#if DEBUG namespace BlahBlah_private { #else namespace { #endif,所以“模块名称空间”仅出现在调试版本中,否则使用真正的匿名名称空间。如果调试器提供了一种解决此问题的好方法,那就太好了。Doxygen也被它弄糊涂了。
克里斯托弗·约翰逊

4
未命名的命名空间实际上不是静态替代。static表示“确实不会将TU链接在一起”。未命名的命名空间意味着“如果从TU之外的父类调用它,它仍将以随机名称导出,”
Erik Aronesty

7

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


1
如果只想在单个翻译单元中使用类型,则在.cpp文件中声明它。无论如何,它将无法从其他翻译部门访问它。
Calmarius 2012年

4
你会认为,不是吗?但是,如果同一应用程序中的另一个翻译单元(= cpp-file)曾经声明了具有相同名称的类型,那么您将遇到相当难以调试的问题:-)。例如,您可能会遇到以下情况:调用另一种方法时,使用其中一种类型的vtable。
avl_sweden 2012年

1
不再弃用。而且不会导出类型def,所以这毫无意义。静态变量对于独立函数和全局变量很有用。未命名的名称空间对于类很有用。
Erik Aronesty

6

根据经验,我只会指出,虽然这是将以前静态的函数放入匿名名称空间的C ++方法,但较早的编译器有时可能会遇到问题。我目前正在为目标平台使用一些编译器,而更现代的Linux编译器可以将函数放入匿名名称空间中。

但是在Solaris上运行的较早版本的编译器(有时直到未指定的将来版本为止)都会被接受,而有时将其标记为错误。错误不是让我担心的原因,而是接受它时可能正在做的事情。因此,在全面实现现代化之前,我们仍然会使用静态(通常是类作用域)的函数,而我们更希望使用匿名名称空间。


3

另外,如果像这样的示例在变量上使用static关键字:

namespace {
   static int flag;
}

在映射文件中看不到


7
然后,您根本不需要匿名名称空间。
Calmarius 2012年

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:已删除未引用的本地函数),以及带有-Wunused-function或-Wall标志的gcc 4.9显示VS 2017仅针对以下内容生成警告未使用的静态函数。gcc 4.9和更高版本以及clang 3.3和更高版本,将为命名空间中未引用的函数产生警告,并为未使用的静态函数产生警告。

GCC 4.9和MSVC 2017的实时演示


2

就我个人而言,出于以下原因,我更喜欢静态函数而不是无名命名空间:

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

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

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

因此,我很高兴看到不再将static用于函数


匿名名称空间中的函数应该具有外部链接。他们只是为了使它们独特而受到重创。static实际上,只有关键字将局部链接应用于函数。而且,肯定只有疯狂的疯子才会为命名空间添加缩进吗?
Roflcopter4'9

0

我只是在阅读您的问题时才了解此功能,所以只能推测一下。与文件级静态变量相比,这似乎具有一些优点:

  • 匿名名称空间可以相互嵌套,从而提供了符号无法逃脱的多重保护。
  • 可以在同一个源文件中放置几个​​匿名名称空间,实际上在同一个文件中创建了不同的静态级别作用域。

我会对是否有人在真实代码中使用匿名名称空间感兴趣。


4
很好的猜测,但是错了。这些名称空间的范围是文件范围的。
Konrad Rudolph

并非完全正确,如果您在另一个名称空间内定义一个匿名名称空间,则该名称空间仍然只有文件范围,并且只能视为位于该名称空间内。尝试一下。
格雷格·罗杰斯

我可能是错的,但是我想不是的,它不是整个文件范围的:仅匿名名称空间之后的代码才能访问它。这是一件微妙的事情,通常,我不想污染具有多个匿名名称空间的源……不过,这可以有用途。
paercebal

0

区别在于整齐的标识符的名称(_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
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

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

您还可以在匿名名称空间中具有嵌套(内联)名称空间

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
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

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
        .align 4
        .type   b, @object
        .size   b, 4

您还可以具有匿名内联名称空间,但据我所知,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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.