C ++的一个特性是能够创建未命名的(匿名)命名空间,如下所示:
namespace {
int cannotAccessOutsideThisFile() { ... }
} // namespace
您会认为这样的功能是无用的 - 因为您无法指定命名空间的名称,所以无法从外部访问其中的任何内容。但是这些未命名的命名空间可以在它们创建的文件中访问,就像你有一个隐含的using子句一样。
我的问题是,为什么或何时使用静态函数会更好?或者它们基本上是两种完全相同的方式吗?
答案 0 :(得分:308)
C ++标准在第7.3.1.1节“未命名的命名空间”中进行了介绍,第2段:
使用static关键字是 在声明对象时不推荐使用 命名空间范围,未命名的命名空间 提供了一个更好的选择
静态仅适用于对象,函数和匿名联合的名称,而不适用于类型声明。
修改强>
弃用static关键字的这种使用(影响翻译单元中变量声明的可见性)的决定已被颠倒(ref)。在这种情况下,使用静态或未命名的命名空间基本上是两种完全相同的方式。有关更多讨论,请参阅this SO问题。
未命名的命名空间仍然具有允许您定义翻译单元本地类型的优点。有关详细信息,请参阅this SO问题。
为了引起我的注意,将信用转到Mike Percy。
答案 1 :(得分:65)
将方法放在匿名命名空间中可以防止您意外违反One Definition Rule,这样您就不必担心命名辅助方法与您可能链接的其他方法相同。
而且,正如luke所指出的,匿名命名空间是静态成员的标准首选。
答案 2 :(得分:33)
有一个边缘情况,静电有惊人的效果(至少对我而言)。 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):
对于postfix-expression是从属名称的函数调用,可以使用找到候选函数 通常的查找规则(3.4.1,3.4.2),除了:
对于使用非限定名称查找(3.4.1)的查找部分,只能找到模板定义上下文中的函数声明。
对于使用关联命名空间(3.4.2)的查找部分,只能找到模板定义上下文或模板实例化上下文中找到的函数声明。
结果是静态和未命名的命名空间成员之间的这种特殊差异不再存在。
答案 3 :(得分:11)
我最近开始在我的代码中用匿名命名空间替换静态关键字但是立即遇到了一个问题,即命名空间中的变量不再可用于我的调试器中进行检查。我使用的是VC60,所以我不知道这是否与其他调试器没有问题。我的解决方法是定义一个“模块”命名空间,在那里我给它命名了我的cpp文件。
例如,在我的XmlUtil.cpp文件中,我为所有模块变量和函数定义了一个名称空间XmlUtil_I {...}。这样我就可以在调试器中应用XmlUtil_I :: qualified来访问变量。在这种情况下,'_I'将它与我可能想在其他地方使用的公共名称空间(如XmlUtil)区分开来。
我认为与真正的匿名方法相比,这种方法的一个潜在缺点是,有人可能通过在其他模块中使用命名空间限定符来违反所需的静态范围。我不知道这是否是一个主要问题。
答案 4 :(得分:7)
C ++ 98标准不推荐使用static关键字。 static的问题在于它不适用于类型定义。它也是在不同上下文中以不同方式使用的重载关键字,因此未命名的命名空间简化了一些事情。
答案 5 :(得分:6)
根据经验,我会注意到,虽然将以前静态函数放入匿名命名空间是C ++方式,但旧编译器有时会遇到问题。我目前正在为我们的目标平台使用一些编译器,而更现代的Linux编译器可以将函数放入匿名命名空间。
但是在Solaris上运行的旧编译器,我们将在未指定的未来版本中使用,有时会接受它,有时会将其标记为错误。错误并不是让我担心的问题,而是它接受时可能正在做的事情。因此,在我们全面开发之前,我们仍然使用静态(通常是类范围的)函数,我们更喜欢匿名命名空间。
答案 6 :(得分:3)
此外,如果在变量上使用静态关键字,例如:
namespace {
static int flag;
}
在映射文件
中不会出现答案 7 :(得分:3)
区别在于整齐的标识符的名称(_ZN12_GLOBAL__N_11bE
与_ZL1b
并不重要,但是它们都被组装为符号表中的本地符号({{ 1}} asm指令)。
.global
对于嵌套的匿名名称空间:
#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
对匿名命名空间的作用为0
inline
inline namespace {
inline namespace {
int a = 3;
}
}
:_ZL1b
表示这是一个错误的标识符。 _Z
表示它是L
中的本地符号。 static
是标识符1
的长度,然后是标识符b
b
_ZN12_GLOBAL__N_11aE
表示这是一个错误的标识符。 _Z
表示这是一个名称空间N
是匿名名称空间名称12
的长度,然后是匿名名称空间名称_GLOBAL__N_1
,然后_GLOBAL__N_1
是名称空间的长度标识符1
,a
是标识符a
和a
关闭位于命名空间中的标识符。
E
与上面相同,除了其中还有另一个命名空间级别_ZN12_GLOBAL__N_11A1aE
答案 8 :(得分:2)
在阅读你的问题时才刚刚学会了这个功能,我只能推测。这似乎比文件级静态变量提供了几个优点:
我有兴趣了解是否有人在实际代码中使用过匿名命名空间。
答案 9 :(得分: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以启用warning C4505: unreferenced local function has been removed)编译此代码,使用-Wunused-function或-Wall标志编译gcc 4.9,表明VS 2017仅会生成警告未使用的静态功能gcc 4.9及更高版本以及clang 3.3及更高版本将为命名空间中的未引用函数生成警告,并对未使用的静态函数发出警告。
答案 10 :(得分:0)
我个人更喜欢静态函数而不是无名空间,原因如下:
从单独的功能定义中可以明显看出,它是编译后翻译单元的私密内容。对于无名空间,您可能需要滚动并搜索以查看函数是否在命名空间中。
命名空间中的函数可能会被某些(较旧的)编译器视为extern。在VS2017中他们仍然是外在的。因此,即使函数位于无名空间中,您仍可能希望将它们标记为静态。
静态函数在C或C ++中的行为非常相似,而无名的命名空间显然只是C ++。如果缩进,无名命名空间也会增加额外的级别,我不喜欢这样:)
所以,我很高兴看到对函数isn't deprecated anymore使用静态。