Unnamed/anonymous namespaces vs. static functions
C++的一个特性是创建匿名(匿名)命名空间的能力,如:
1 2 3 | namespace { int cannotAccessOutsideThisFile() { ... } } // namespace |
您可能会认为这样的特性是无用的——因为您不能指定名称空间的名称,所以从外部访问其中的任何内容都是不可能的。但是这些未命名的名称空间在创建它们的文件中是可以访问的,就像您对它们有一个隐式的using子句一样。
我的问题是,为什么或何时比使用静态函数更好?或者它们本质上是两种做同样事情的方法?
C++标准读取7.3.1.1未命名命名空间,第2段:
The use of the static keyword is
deprecated when declaring objects in a
namespace scope, the unnamed-namespace
provides a superior alternative.
静态只适用于对象、函数和匿名联合的名称,而不适用于类型声明。
编辑:
拒绝使用静态关键字(影响翻译单元中变量声明的可见性)的决定已被撤销(ref)。在这种情况下,使用静态或未命名的名称空间实质上是两种执行完全相同操作的方法。有关更多讨论,请参阅此问题。
未命名的名称空间仍然具有允许您定义翻译单元本地类型的优势。请参阅此问题了解更多详细信息。
迈克·珀西把这件事引起了我的注意。
将方法放在匿名命名空间中可以防止意外违反一个定义规则,使您不必担心将助手方法命名为与可能链接的其他方法相同的方法。
而且,正如Luke所指出的,标准首选匿名名称空间而不是静态成员。
有一个边缘案例,静态有一个惊人的效果(至少对我来说是这样)。C++ 03标准在14.4.4.2/1中表示:
For a function call that depends on a template parameter, if the function name is an unqualified-id but not a template-id, the candidate functions are found using the usual lookup rules (3.4.1, 3.4.2) except that:
- For the part of the lookup using unqualified name lookup (3.4.1), only function declarations with external linkage from the template definition context are found.
- For the part of the lookup using associated namespaces (3.4.2), only function declarations with external linkage found in either the template definition context or the template instantiation context are found.
...
下面的代码将调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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++编译器(即支持EDCOX1的2),EDCOX1×3 }关键字仍然具有任何其他方式不可用的功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | // 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的模板中找不到未命名命名空间中的函数的唯一方法是使其成为
现代C++的更新
对于C++"11",未命名命名空间的成员隐含地具有内部链接(3.5/4):
An unnamed namespace or a namespace declared directly or indirectly within an unnamed namespace has internal linkage.
但同时,更新了4.64.2/1,以消除提及的链接(这取自C++ 14):
For a function call where the postfix-expression is a dependent name, the candidate functions are found using
the usual lookup rules (3.4.1, 3.4.2) except that:
For the part of the lookup using unqualified name lookup (3.4.1), only function declarations from the template definition context are found.
For the part of the lookup using associated namespaces (3.4.2), only function declarations found in either the template definition context or the template instantiation context are found.
结果是静态和未命名的命名空间成员之间的这种特殊差异不再存在。
我最近开始用代码中的匿名名称空间替换静态关键字,但立即遇到了一个问题,名称空间中的变量在我的调试器中不再可供检查。我使用的是VC60,所以我不知道这是否是其他调试器的问题。我的解决方法是定义一个"module"名称空间,在那里我给它命名了我的cpp文件。
例如,在xmlutil.cpp文件中,我定义了一个名称空间xmlutil_…}对于我所有的模块变量和函数。这样我就可以在调试器中应用xmlutil_i::qualification来访问变量。在这种情况下,"u i"将它与公共命名空间(如xmlutil)区分开来,我可能希望在其他地方使用它。
我认为与真正匿名的方法相比,这种方法的一个潜在缺点是,有人可以通过在其他模块中使用名称空间限定符来违反所需的静态范围。不过,我不知道这是否是一个主要问题。
使用静态关键字用于此目的是由C++ 98标准表示的。静态的问题是它不适用于类型定义。它也是一个重载的关键字,在不同的上下文中以不同的方式使用,因此未命名的名称空间简化了一些事情。
从经验中,我将注意到,虽然C++将以前的静态函数放入匿名命名空间,但旧编译器有时会遇到问题。我目前为我们的目标平台使用一些编译器,而更现代的Linux编译器可以将函数放在匿名名称空间中。
但是在Solaris上运行的一个旧的编译器,在未来的某个未知版本发布之前,我们都会接受它,而其他时候会将它标记为一个错误。错误并不是让我担心的,而是当它接受错误时可能会发生的事情。因此,在全面实现现代化之前,我们仍然在使用静态(通常是类范围的)函数,我们更喜欢使用匿名名称空间。
此外,如果对类似以下示例的变量使用static关键字:
1 2 3 | namespace { static int flag; } |
在映射文件中看不到它
在编译以下代码时,可以看到匿名命名空间和静态函数之间的编译器特定差异。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | #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函数或-Wall标志的GCC 4.9表明,VS 2017将仅为未使用的静态函数生成警告。GCC 4.9及更高版本,以及Clang 3.3及更高版本,将为命名空间中的未引用函数生成警告,并为未使用的静态函数生成警告。
GCC 4.9和MSVC 2017现场演示
我刚刚在阅读你的问题时才了解到这个特性,我只能推测。与文件级静态变量相比,这似乎提供了几个优势:
- 匿名名称空间可以彼此嵌套,提供多个级别的保护,符号无法从中逃脱。
- 几个匿名名称空间可以放在同一个源文件中,从而在同一个文件中创建不同的静态级别范围。
如果有人在真正的代码中使用了匿名名称空间,我会感兴趣的。
我个人更喜欢静态函数而不是无名称名称空间,原因如下:
仅从函数定义就可以清楚地看到,它对编译它的翻译单元是私有的。对于无名称的名称空间,您可能需要滚动和搜索以查看函数是否在名称空间中。
某些(旧的)编译器可能会将命名空间中的函数视为外部函数。在VS2017中,它们仍然是外部的。因为这个原因,即使函数在无名称的名称空间中,您仍然可能希望将它们标记为静态的。
静态函数在C或C++中的行为非常相似,而无名命名空间显然是C++的。无名称命名空间还添加了额外的级别,如果缩进,我不喜欢这样做:)
所以,我很高兴地看到,对函数使用static已经不受欢迎了。