作用域

命名空间

命名空间里的内容不缩进。

建议 6.1.1 对于 cpp 文件中不需要导出的变量,常量或者函数,请使用匿名 namespace 封装或者用 static 修饰

在 C++ 2003 标准规范中,使用 static 修饰文件作用域的变量,函数等被标记为 deprecated 特性,所以更推荐使用匿名 namespace。

主要原因如下:

  • static 在 C++中已经赋予了太多的含义,静态函数成员变量,静态成员函数,静态全局变量,静态函数局部变量,每一种都有特殊的处理。
  • static 只能保证变量,常量和函数的文件作用域,但是 namespace 还可以封装类型等。
  • 统一 namespace 来处理 C++的作用域,而不需要同时使用 static 和 namespace 来管理。
  • static 修饰的函数不能用来实例化模板,而匿名 namespace 可以。但是不要在 .h 中使用中使用匿名 namespace 或者 static。
1. // Foo.cpp

3. namespace {
4. const int kMaxCount = 20;
5. void InternalFun(){};
6. }

8. void Foo::Fun() {
9. int i = kMaxCount;

11. InternalFun();
12. }

规则 6.1.1 不要在头文件中或者#include 之前使用 using 导入命名空间

说明:使用 using 导入命名空间会影响后续代码,易造成符号冲突,所以不要在头文件以及源文件中的#include 之前使用 using 导入命名空间。示例:

1. // 头文件a.h
2. namespace namespacea {
3. int Fun(int);
4. }
1. // 头文件b.h
2. namespace namespaceb {
3. int Fun(int);
4. }

6. using namespace namespaceb;

8. void G() {
9. Fun(1);
10. }
1. // 源代码a.cpp
2. #include "a.h"
3. using namespace namespacea;
4. #include "b.h"

6. void main() {
7. G(); // using namespace namespacea在#include “b.h”之前,引发歧义:namespacea::Fun,namespaceb::Fun调用不明确
8. }

对于在头文件中使用 using 导入单个符号或定义别名,允许在模块自定义名字空间中使用,但禁止在全局名字空间中使用。

1. // foo.h

3. #include <fancy/string>
4. using fancy::string;  // Bad,禁止向全局名字空间导入符号

6. namespace foo {
7. using fancy::string;  // Good,可以在模块自定义名字空间中导入符号
8. using MyVector = fancy::vector<int>;  // Good,C++11可在自定义名字空间中定义别名
9. }

全局函数和静态成员函数

建议 6.2.1 优先使用命名空间来管理全局函数,如果和某个 class 有直接关系的,可以使用静态成员函数

说明:非成员函数放在名字空间内可避免污染全局作用域, 也不要用类+静态成员方法来简单管理全局函数。 如果某个全局函数和某个类有紧密联系, 那么可以作为类的静态成员函数。

如果你需要定义一些全局函数,给某个 cpp 文件使用,那么请使用匿名 namespace 来管理。

1. namespace mynamespace {
2. int Add(int a, int b);
3. }

5. class File {
6. public:
7. static File CreateTempFile(const std::string& fileName);
8. };

全局常量和静态成员常量

建议 6.3.1 优先使用命名空间来管理全局常量,如果和某个 class 有直接关系的,可以使用静态成员常量

说明:全局常量放在命名空间内可避免污染全局作用域, 也不要用类+静态成员常量来简单管理全局常量。 如果某个全局常量和某个类有紧密联系, 那么可以作为类的静态成员常量。

如果你需要定义一些全局常量,只给某个 cpp 文件使用,那么请使用匿名 namespace 来管理。

1. namespace mynamespace {
2. const int kMaxSize = 100;
3. }

5. class File {
6. public:
7. static const std::string kName;
8. };

全局变量

建议 6.4.1 尽量避免使用全局变量,考虑使用单例模式

说明:全局变量是可以修改和读取的,那么这样会导致业务代码和这个全局变量产生数据耦合。

1. int counter = 0;

3. // a.cpp
4. counter++;

6. // b.cpp
7. counter++;

9. // c.cpp
10. cout << counter << endl;

使用单实例模式

1. class Counter {
2. public:
3. static Counter& GetInstance() {
4. static Counter counter;
5. return counter;
6. }  // 单实例实现简单举例

8. void Increase() {
9. value++;
10. }

12. void Print() const {
13. std::cout << value << std::endl;
14. }

16. private:
17. Counter() : value(0) {}

19. private:
20. int value;
21. };

23. // a.cpp
24. Counter::GetInstance().Increase();

26. // b.cpp
27. Counter::GetInstance().Increase();

29. // c.cpp
30. Counter::GetInstance().Print();

实现单例模式以后,实现了全局唯一一个实例,和全局变量同样的效果,并且单实例提供了更好的封装性。

例外:有的时候全局变量的作用域仅仅是模块内部,这样进程空间里面就会有多个全局变量实例,每个模块持有一份,这种场景下是无法使用单例模式解决的。