为什么要使用未命名的命名空间,有什么好处?

354

我刚加入了一个新的C++软件项目,正在努力理解其设计。该项目经常使用未命名命名空间。例如,在类定义文件中可能会出现以下内容:

// newusertype.cc
namespace {
  const int SIZE_OF_ARRAY_X;
  const int SIZE_OF_ARRAY_Y;
  bool getState(userType*,otherUserType*);
}

newusertype::newusertype(...) {...

在设计中有什么考虑因素会导致使用未命名的命名空间?有哪些优缺点?

6个回答

275

未命名命名空间是一种实用工具,用于使标识符翻译单位本地化。它们的行为就像你会选择一个唯一的名称作为每个翻译单位的命名空间一样:

namespace unique { /* empty */ }
using namespace unique;
namespace unique { /* namespace body. stuff in here */ }

通过使用空的命名空间,可以在命名空间内引用如::name这样在该命名空间中定义的标识符,因为using指令已经生效,这一步骤是重要的。

这意味着你可以有名为(例如)help的自由函数,它们可以存在于多个翻译单元中,并且它们不会在链接时发生冲突。其效果几乎与在C中使用的static关键字相同,该关键字可以放置在标识符的声明中。未命名的命名空间是更好的选择,甚至可以使类型成为翻译单元本地的。

namespace { int a1; }
static int a2;

a的两个实例都是翻译单元本地的,不会在链接时发生冲突。但是,匿名命名空间中的a1具有唯一名称。

阅读来自comeau-computing的优秀文章:为什么使用未命名命名空间而不是静态? (Archive.org 镜像)。


1
你可以解释一下static的关系吗?能否请您与__attribute__ ((visibility ("hidden")))进行比较? - phinz
2
那个链接到Comeau的网站似乎已经失效了。 - raylu

130

在匿名命名空间中拥有某些内容意味着它局限于这个翻译单元(.cpp文件和所有包含的内容),这意味着如果在其他地方定义了具有相同名称的另一个符号,则不会违反一次定义规则(ODR)。

这与C语言中具有静态全局变量或静态函数的方式相同,但也可以用于类定义(应该使用而不是C++中的static)。

同一文件中的所有匿名命名空间都被视为相同的命名空间,而不同文件中的所有匿名命名空间是不同的。匿名命名空间等效于:

namespace __unique_compiler_generated_identifer0x42 {
    ...
}
using namespace __unique_compiler_generated_identifer0x42;

35

未命名的命名空间将类、变量、函数和对象的访问限制在定义它的文件中。未命名的命名空间功能类似于C/C++中的static关键字。
static关键字将全局变量和函数的访问限制在定义它们的文件中。
未命名的命名空间和static关键字之间存在差异,因此未命名的命名空间具有优势。 static关键字可以用于变量、函数和对象,但不能用于用户定义的类。
例如:

static int x;  // Correct 

但是,

static class xyz {/*Body of class*/} //Wrong
static struct structure {/*Body of structure*/} //Wrong

但是使用未命名的命名空间也可以实现相同的效果。例如:

 namespace {
           class xyz{/*Body of class*/}
           struct structure {/*Body of structure*/}
  } //Correct

                   

4
什么是“静态结构”?为什么它必须是静态的? - smac89
3
他们可能指的是“静态结构体”。 - adentinger

19

除了其他回答,使用匿名命名空间还可以提高性能。由于命名空间中的符号不需要任何外部链接,编译器可以更自由地对命名空间内代码进行积极优化。例如,在循环中多次调用的函数可以被内联而不会对代码大小产生任何影响。

例如,使用匿名命名空间时,以下代码在我的系统上占运行时间的约70%(x86-64 gcc-4.6.3和-O2;请注意,add_val中额外的代码使编译器不想将其包含两次)。

#include <iostream>

namespace {
  double a;
  void b(double x)
  {
    a -= x;
  }
  void add_val(double x)
  {
    a += x;
    if(x==0.01) b(0);
    if(x==0.02) b(0.6);
    if(x==0.03) b(-0.1);
    if(x==0.04) b(0.4);
  }
}

int main()
{
  a = 0;
  for(int i=0; i<1000000000; ++i)
    {
      add_val(i*1e-10);
    }
  std::cout << a << '\n';
  return 0;
}

6
太美好了,难以置信——我在gcc 4-1-2上尝试了这个片段,并使用-O3优化,在有和没有命名空间语句的情况下进行测试:->得到相同的时间(使用-O3为3秒,不使用-O3为4秒)。 - Theo
2
这段代码故意复杂,试图说服编译器不要将b和add_val内联到main中。O3优化会大量使用内联,而不考虑代码膨胀的成本。然而,仍然有可能存在O3无法内联add_val的函数。你可以尝试让add_val更加复杂,或在不同情况下从main中多次调用它。 - xioxox
6
@Daniel:我错过了什么?你说你将“-O3”与自身进行比较,然后又说3秒和4秒是“相同的时间”。这两者都没有任何意义。我怀疑真正的解释会有道理,但是它是什么呢? - underscore_d
@underscore_d 回答中指出,两种情况下都使用了 -O2,而不是 -O3。不同的优化级别可能会有不同的行为。此外,不同的编译器版本可能会有不同的行为(回答可能会过时)。 - Paul Stelian
1
@PaulStelian 我知道,但很明显我回复的不是xioxox的答案,而是Theo的评论(尽管他的名字可能已经更改,或者我搞混了)。 - underscore_d

16

这个例子表明,你加入的项目中的人们不理解匿名命名空间 :)

namespace {
    const int SIZE_OF_ARRAY_X;
    const int SIZE_OF_ARRAY_Y;

由于const对象已经具有静态链接性,因此它们不需要在匿名命名空间中,这样就不可能与另一个翻译单元中同名的标识符发生冲突。

    bool getState(userType*,otherUserType*);
}

其实这是一种悲观情况:getState() 具有外部链接。通常最好采用静态链接,因为这样不会污染符号表。最好写成

static bool getState(/*...*/);

我也曾经遇到这个坑(标准里的措辞似乎暗示文件静态变量不如匿名命名空间),但在像KDE这样的大型C++项目中工作,你会有很多人帮你纠正错误 :)


13
自从C++11以来,未命名的命名空间具有内部链接(标准第3.5节或http://en.cppreference.com/w/cpp/language/namespace#Unnamed_namespaces)。 - Emile Vrijdags
14
这些不需要在匿名命名空间中 Technically, sure - 但是,把它们放在匿名命名空间中作为其语义的可视提醒并使稍后取消const更加容易(甚至更加)无妨。我怀疑这意味着原帖作者的团队“不理解”任何内容!此外,有关匿名命名空间中函数具有外部链接的说法在C++11及以后是错误的。据我所知,他们解决了以前模板参数需要外部链接的问题,因此可以允许未命名命名空间(能够包含模板参数)具有内部链接。 - underscore_d

13
一个匿名命名空间使得其中的变量、函数、类等只在该文件中可用。在你的示例中,它是避免全局变量的一种方式。没有运行时或编译时性能差异。
除了“我想把这个变量、函数、类等设置为公共的还是私有的?”之外,优点和缺点并不明显。

3
这里可能会有性能差异 - 请参见我的答案。它允许编译器更好地优化代码。 - xioxox
3
你说得没错,至少在当今的C++中是这样。然而,在C++98/C++03中,为了将东西用作模板参数,必须具有外部链接性。由于匿名命名空间中的内容可用作模板参数,因此它们将具有外部链接性(至少在C++11之前),即使没有办法从文件外部引用它们。我认为可能有一些方法可以规避这个问题,因为标准只要求事情表现得好像规则被执行了;有时可以在不真正执行规则的情况下做到这一点。 - Max Lybbert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接