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

Hea*_*eek 486 c++ namespaces

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

namespace {
    int cannotAccessOutsideThisFile() { ... }
} // namespace
Run Code Online (Sandbox Code Playgroud)

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

我的问题是,为什么或何时使用静态函数会更好?或者他们基本上是两种做同样事情的方式?

luk*_*uke 321

C++标准在第7.3.1.1节"未命名的命名空间"中进行了介绍,第2段:

在声明命名空间作用域中的对象时,不推荐使用static关键字,unnamed-namespace提供了一个更好的替代方法.

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

编辑:

弃用static关键字的这种使用(影响翻译单元中变量声明的可见性)的决定已被颠倒(ref).在这种情况下,使用静态或未命名的命名空间基本上是两种完全相同的方式.有关更多讨论,请参阅 SO问题.

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

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

  • Head Geek询问仅针对函数使用的静态关键字.应用于在命名空间作用域中声明的实体的static关键字指定其内部链接.在匿名名称空间中声明的实体具有外部链接(C++/3.5),但是它保证存在于唯一命名的作用域中.这种匿名的无名空间有效地隐藏了它的声明,使其只能从翻译单元中访问.后者有效地以与static关键字相同的方式工作. (39认同)
  • 因为这个答案在Google上作为"c ++匿名命名空间"的最高结果出现,所以应该注意静态的使用不再被弃用.请参阅http://stackoverflow.com/questions/4726570/deprecation-of-the-static-keyword-no-more和http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html #1012了解更多信息. (21认同)
  • 在C++设计组合中,那些静态关键字被弃用的人可能从未在大型真实世界系统中使用过庞大的C代码......(如果它包含很多带有大量注释的声明,你可以立即看到静态关键字而不是匿名命名空间块). (17认同)
  • 外部联动的缺点是什么?这会影响内联吗? (5认同)
  • @ErikAronesty 这听起来不对。你有一个可重复的例子吗?从 C++11 开始 - 甚至在某些编译器之前 - 未命名的“命名空间”隐式具有内部链接,因此应该没有区别。通过在 C++11 中将此作为一项要求,解决了以前可能因措辞不当而引起的任何问题。 (5认同)
  • @ErikAronesty为什么? (4认同)
  • 未命名的命名空间会减慢链接速度。使用静态 (2认同)
  • 外部链接意味着它不是一个私有符号,它也是链接器的更多工作 (2认同)

haz*_*zen 69

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

而且,正如luke所指出的,匿名命名空间是静态成员的标准首选.

  • 我指的是静态独立功能(即文件作用域函数),而不是静态成员函数。静态独立功能与未命名空间中的功能非常相似,因此是个问题。 (2认同)
  • 啊; 好吧,ODR仍然适用.编辑删除段落. (2认同)
  • @NikitaVorontsov 守卫可能会阻止将相同的标头包含到同一翻译单元中,但它允许在不同的翻译单元中存在多个定义。这可能会导致“多重定义”链接器错误。 (2认同)

Ric*_*den 36

有一个边缘情况静态具有惊人的效果(至少对我而言).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);
}
Run Code Online (Sandbox Code Playgroud)

本身这可能不是什么大不了的事,但它确实强调了对于完全兼容的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);
}
Run Code Online (Sandbox Code Playgroud)

确保在使用ADL的模板中找不到未命名的命名空间中的函数的唯一方法就是实现它static.

更新现代C++

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

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

但与此同时,更新了14.6.4.2/1以删除链接的提及(这取自C++ '14):

对于postfix-expression是从属名称的函数调用,使用通常的查找规则(3.4.1,3.4.2)找到候选函数,除了:

  • 对于使用非限定名称查找(3.4.1)的查找部分,仅找到模板定义上下文中的函数声明.

  • 对于使用关联命名空间(3.4.2)的查找部分,仅找到在模板定义上下文或模板实例化上下文中找到的函数声明.

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

  • 导出关键字是不应该冷死?唯一支持"出口"的编译器是实验性的,除非出现意外,否则"出口"甚至不会在其他情况下实施,因为出现了意想不到的副作用(此外还没有预期) (3认同)
  • 爱迪生设计集团(EDG)的前端不过是实验性的.它几乎可以肯定是世界上最符合标准的C++实现.英特尔C++编译器使用EDG. (3认同)
  • 参见Herb Sutter关于子喷气的文章:http://www.gotw.ca/publications/mill23-x.htm (2认同)

小智 11

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

例如,在我的XmlUtil.cpp文件中,我为所有模块变量和函数定义了一个名称空间XmlUtil_I {...}.这样我就可以在调试器中应用XmlUtil_I :: qualified来访问变量.在这种情况下,'_ I'将它与我可能想在别处使用的公共名称空间(如XmlUtil)区分开来.

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

  • 我也是这样做的,但使用`#if DEBUG命名空间BlahBlah_private {#else namespace {#endif`,所以"模块命名空间"只出现在调试版本中,否则会使用真正的匿名命名空间.如果调试器提供了一个很好的方法来处理它,那将是很好的.Doxygen也被它弄糊涂了. (7认同)
  • 未命名的命名空间实际上不是静态的替代品.静态意味着"真的,这永远不会与TU联系在一起".未命名的命名空间意味着"它仍然作为随机名称导出,以防它从TU之外的父类调用"... (3认同)

Lew*_*sey 10

区别在于重整标识符的名称(_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
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

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

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

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
Run Code Online (Sandbox Code Playgroud)

您还可以拥有匿名内联命名空间,但据我所知,inline对匿名命名空间的影响为 0

inline namespace {
   inline namespace {
       int a = 3;
    }
}
Run Code Online (Sandbox Code Playgroud)

_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,前缀长度为A1。匿名命名空间都有这个名字_GLOBAL__N_1


Fir*_*aad 7

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

  • 你会想,不是吗?但是,如果同一个应用程序中的另一个转换单元(= cpp-file)声明了一个具有相同名称的类型,那么您将面临相当难以调试的问题:-).例如,您最终可能会遇到在调用另一个类型的方法时使用其中一个类型的vtable的情况. (4认同)
  • 不再弃用。并且类型定义不会被导出,所以这是没有意义的。静态对于独立函数和全局变量很有用。未命名的命名空间对于类很有用。 (2认同)

Pav*_*l P 7

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

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

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

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

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


Don*_*eld 6

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

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