何时在C++中使用静态(而非未命名的命名空间)是合适的?

7

我整天都在阅读关于无名命名空间的文章,大多数文章都解释了何时应该使用无名命名空间而不是static关键字。但是我仍然有一个重要问题:什么情况下使用静态关键字比较合适?毕竟它并没有完全被弃用,那么对于具有静态函数的头文件,现在是应该将它们放入无名命名空间中吗?

#ifndef HEADER_H
#define HEADER_H

static int func() {
  ...
}

// versus:

namespace {
  int func() {
    ...
  }
};

#endif // HEADER_H 

或者考虑静态成员函数呢?

问候


7
在头文件中放置静态变量(或未命名的命名空间)似乎是适得其反的。如果它们是静态的,为什么不直接放在源文件中呢? - Martin York
4个回答

5
标准的确切措辞如下:
在命名空间范围内声明对象时,使用static关键字已被弃用。
头文件中的函数应该是inline,而不是static或未命名的命名空间。inline表示你的程序最多只会有一个函数副本,而其他方法将从包含头文件的每个文件中获得单独的副本。除了代码膨胀外,如果函数包含函数静态数据,则可能会导致不正确的行为。(编辑:除非该函数应该在不同的编译单元中具有不同的定义,也许是由于在包括头文件之前定义了不同的预处理器宏。在这种情况下,最好的方法是根本不要包含它,而是将其埋藏在没有标记的坟墓中,并用一根长木桩刺穿它的邪恶之心。)
除常量外,数据对象通常根本不应在头文件中定义,只需声明extern即可。
静态成员函数是另一种情况,你必须在那里使用static,因为没有其他方式来声明它们。由于它不在命名空间范围内,因此该用法并未被弃用。
更新:C++11已经取消了弃用,因此不再有特别的理由优先选择未命名的命名空间而不是static。但是,除非你在做一些奇怪的事情,否则仍然不应在头文件中使用任何一个。

数据对象根本不应该在头文件中定义,只有当它们需要外部链接时才声明为extern。这是不精确的。正如你所说的,“数据对象”默认具有外部链接,除非它们是const。因此,extern是必需的,以使它们的声明仅仅是一个声明而不是一个定义,这是默认情况。但是,如果同时存在extern和初始化程序,则它就是一个定义。 - Armen Tsirunyan
数据对象通常需要在头文件中进行定义。这包括常量(也是对象)。我想对于C++新手来说,避免在头文件中定义(静态类成员)非整数类型的常量是一个好建议。有经验的程序员可以使用例如模板化常量技巧来解决这个问题。另一个技巧是将函数返回指向本地静态常量的引用。 - Cheers and hth. - Alf
1
好的,对于外部和静态成员函数的解释很好。关于内联,我一直认为它会用函数代码本身替换函数调用,因此最终会得到很多副本?! - haribo
4
“或者在未命名的命名空间中。” - 不行,这太糟糕了。未命名的命名空间是用于 .cpp 文件的,它们想要将数据、函数或类型设为TU本地,以便可以使用常见名称且确保不会冲突。但是如果将它们放到头文件中,就会污染该私有环境! - Johannes Schaub - litb
在C++11中,不再强烈反对使用static在命名空间范围内。标准也不再鼓励使用未命名的命名空间而非static - Alok Save
显示剩余5条评论

3
我不知道在命名空间范围内使用静态变量相比未命名的命名空间有什么优势。建议使用未命名的命名空间,就像上面的示例一样。实际上,在上面的示例中,我无法看出为什么需要静态或未命名的命名空间。也许是内联?而静态成员函数与命名空间范围内的静态无关。静态成员函数(和非函数成员)仍然有效。

在这个例子中,需要使用 static 关键字以避免函数具有外部链接。由于它将在使用头文件的每个模块中实现,如果没有 static 关键字,链接器可能会报错。 - Alexander Rautenberg
@Alexander:好的,inline将允许函数的多个定义。在我看来,这是一种更优越的选择。然而,如果需要“内部链接”,那么未命名的命名空间会更好,至少因为它没有被弃用。 - Armen Tsirunyan
4
inline 允许函数有多个相同的定义。static允许多个不同的定义,因为每个翻译单元都有其具有内部链接的函数,只是它们碰巧用相同的名称引用。因此,如果函数 func 的定义中有一些依赖于某些 #define(例如 assert)的内容,并且您希望能够合法地组合包含具有不同 #define 值的头文件的不同翻译单元,则需要使用 static 或未命名的命名空间。官方上来说,inline 不行,尽管在实践中可能有效。 - Steve Jessop
如果确切需要的是“内部链接”,则未命名的命名空间是不够的。虽然这可能是一个技术细节,但未命名命名空间的成员(除其他命名空间外)默认具有“外部链接”。 - CB Bailey
我的 FCD 显示:“尽管未命名命名空间中的实体可能具有外部链接,但它们被一个唯一于其翻译单元的名称限定, 因此永远不会从任何其他翻译单元中看到。” - Ben Voigt

3
在头文件中通常没有必要指定内部链接或使用匿名命名空间。
在单独编译的实现文件中,您可以使用static或匿名命名空间来避免链接级别名称冲突或客户端代码依赖于实现细节。匿名命名空间使您可以具有外部链接,这是模板参数所需的,并且还支持类定义。但最终这只是一个实用性和个人喜好问题,需要根据具体情况而定。
对于成员函数而言,static与链接规范无关。 static成员函数始终具有外部链接。

0

static在使用某些工具时可能更可取。它在大多数文本编辑器中的自动缩进功能方面表现得更好。当你不得不避免使用一些有用的东西,因为它不能与真正支持它的工具一起使用时,这有点令人沮丧,但我保证你会克服它。

以下是一个示例问题,它暗示了在调试部分可能会遇到的痛苦:

在Visual Studio调试器中查看命名空间全局变量?

你可能不必费太大力气就能找到更多问题,但调试器问题足以让我完全放弃命名空间,尽可能地避免使用,所以我从未深入研究过。

我的个人建议是,对于存在时间不长的代码,最好选择static。它实际上与未命名的命名空间执行相同的操作,但在所有工具中平均而言,它得到了更好的支持。理论上,有一天它将完全消失,但我很高兴公开承认,我确信那一天永远不会到来。同时,你也可以减少一些痛苦。


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