作用域

命名空间

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

建议 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
  2. namespace {
  3. const int kMaxCount = 20;
  4. void InternalFun(){};
  5. }
  6. void Foo::Fun() {
  7. int i = kMaxCount;
  8. InternalFun();
  9. }

规则 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. }
  5. using namespace namespaceb;
  6. void G() {
  7. Fun(1);
  8. }
  1. // 源代码a.cpp
  2. #include "a.h"
  3. using namespace namespacea;
  4. #include "b.h"
  5. void main() {
  6. G(); // using namespace namespacea在#include “b.h”之前,引发歧义:namespacea::Fun,namespaceb::Fun调用不明确
  7. }

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

  1. // foo.h
  2. #include <fancy/string>
  3. using fancy::string; // Bad,禁止向全局名字空间导入符号
  4. namespace foo {
  5. using fancy::string; // Good,可以在模块自定义名字空间中导入符号
  6. using MyVector = fancy::vector<int>; // Good,C++11可在自定义名字空间中定义别名
  7. }

全局函数和静态成员函数

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

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

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

  1. namespace mynamespace {
  2. int Add(int a, int b);
  3. }
  4. class File {
  5. public:
  6. static File CreateTempFile(const std::string& fileName);
  7. };

全局常量和静态成员常量

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

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

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

  1. namespace mynamespace {
  2. const int kMaxSize = 100;
  3. }
  4. class File {
  5. public:
  6. static const std::string kName;
  7. };

全局变量

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

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

  1. int counter = 0;
  2. // a.cpp
  3. counter++;
  4. // b.cpp
  5. counter++;
  6. // c.cpp
  7. cout << counter << endl;

使用单实例模式

  1. class Counter {
  2. public:
  3. static Counter& GetInstance() {
  4. static Counter counter;
  5. return counter;
  6. } // 单实例实现简单举例
  7. void Increase() {
  8. value++;
  9. }
  10. void Print() const {
  11. std::cout << value << std::endl;
  12. }
  13. private:
  14. Counter() : value(0) {}
  15. private:
  16. int value;
  17. };
  18. // a.cpp
  19. Counter::GetInstance().Increase();
  20. // b.cpp
  21. Counter::GetInstance().Increase();
  22. // c.cpp
  23. Counter::GetInstance().Print();

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

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