unnamed namespace and using declaration

在工作中经常看到这样的代码。好奇为什么要用unnamed namespace,以及在unnamed namespace里用using会有什么效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
namespace NS_A1 {
namespace NS_A2 {
namespace NS_A3 {

namespace {
using NS_B1::NS_B2::NS_B3::ClassB;

// some local variables
}

class A {
// ... use ClassB methods
}

}
}
}

unnamed namespace scope

unnamed namespace不同于常规的namespace之处

首先想搞明白的就是unnamed namespace的scope(作用域)。

unnamed namespace只作用在当前的文件,也就是说如果两个cpp文件里都有unnamed namespace,那么他们没有任何关联。

如果unnamed namespace是定义在头文件里,那么引用头文件的cpp文件用到的unnamed namespace里的成员也是仅属于当前的cpp文件。也就是说,比如有两个cpp文件都用到头文件unnamed namespace的某个变量a,那么相当于他们都创建了只能在自己cpp文件使用的a变量,各种操作当然只会对自己的a起作用。

示例:

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
// main.cpp
#include <iostream>
#include "nsInHeader.h"
#include "func.h"
int main()
{
std::cout<<"in main, before change, a: " << a << "\n";
func();
a = 1;
std::cout<<"in main, after change, a: " << a << "\n";
func();
return 0;
}

// nsInHeader.h
namespace {
int a = 0;
}

// func.cpp
#include "nsInHeader.h"
#include <iostream>
#include "func.h"
void func() {
std::cout << "in func, a: " << a << "\n";
}

// func.h
void func();

运行输出结果:

1
2
3
4
in main, before change, a: 0
in func, a: 0
in main, after change, a: 1
in func, a: 0

定义在unnamed namespace里的成员的scope

定义在unnamed namespace里的成员跟这个unnamed namespace本身的scope相同。

1
2
3
4
5
6
7
8
// C++ primer例子,英文第五版p791
int i; // global declaration for i
namespace {
int i;
}
// ambiguous: defined globally and in an unnested, unnamed namespace
i = 10;

如果unnamed namespace定义在一个文件的最外层scope,相当于定义在这个文件的global scope,那unnamed namespace的成员也相当于定义在global scope。

这也是为什么unnamed namespace能够取代static变量。定义在global scope的unnamed namespace里的成员跟用static声明的变量都只在当前文件里可用,对其他文件不可见。

通过例子看普通namespace和unnamed namespace的区别

分别在两个cpp文件里定义namespace NS1,并在namespace里定义一个变量:

1
2
3
4
5
6
7
8
9
10
// ns1.cpp
namespace NS1 {
int a = 0;
}

// main.cpp
// reopen the namespace NS1:
namespace NS1 {
int a = 0;
}
这时候编译不会有问题,但是link的时候会报错: "multiple definition of NS1::a;".

但是如果改成下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ns1.cpp
namespace NS1 {
namespace{
int a = 0;
}
}

// main.cpp
// reopen the namespace NS1:
namespace NS1 {
namespace{
int a = 0;
}
}
就不会报错了。

这是因为普通的namespace可以展开到多个文件,但是就如之前所说,unnamed namespace只在当前的文件有效。

Using

把别的namespace的成员引入当前的namespace,有两种using的用法:using declaration和using Directives。 这里用到的是using declaration所以就只讨论它。关于using directive,我发现了一篇很好的文章,放在后面了。

using declaration

In namespace and block scope

Using-declarations introduce a member of another namespace into the current namespace or block scope.

https://en.cppreference.com/w/cpp/language/using_declaration

之前提到的using NS_B1::NS_B2::NS_B3::ClassB;就属于using declaration。它只是把其它namespace的成员引入到当前的scope。

而“定义在unnamed namespace里的成员跟这个unnamed namespace本身的scope相同”。

也就是说,其实using declaration放不放在unnamed namespace里,效果都是一样的。

那么我猜在工作中看到using declaration放在unnamed namespace只是顺便;因为unnamed namespace里还定义了别的变量和函数,这些成员只在当前文件可见;别的文件如果用了相同的enclosing namespace的,也定义了类似的变量和函数,放在unnamed namespace里就不会导致命名冲突。

include

#include < h-char-sequence > new-line (1)

#include " q-char-sequence " new-line (2)

  1. Searches for a header identified uniquely by h-char-sequence and replaces the directive by the entire contents of the header.

  2. Searches for a source file identified by q-char-sequence and replaces the directive by the entire contents of the source file. It may fallback to (1) and treat q-char-sequence as a header identifier.

https://en.cppreference.com/w/cpp/preprocessor/include

在preprocessor处理cpp文件的时候,include directive会把头文件的全部内容拿过来。

Translation Unit

一个translation unit就是一个cpp文件和它引用的头文件。 https://stackoverflow.com/questions/1106149/what-is-a-translation-unit-in-c

所以,如果不把using declaration放在头文件里并被另一个cpp文件引用的话,这个using declaration对于另一个cpp文件来说就是不可见的。

using Directives

https://quuxplusone.github.io/blog/2020/12/21/using-directive/

这篇文章说用using directive会把target namespace的成员引入到target namespace和current scope的least common ancestor的scope里。

而且这个引入的效果仅限在current scope(以及它包含的scope)里。在外层和其他的的scope里,target namespace的成员依然是不可见的。

而且最后的建议就是不要用using Directives。