• 作用域
    • 命名空间
      • 建议 6.1.1 对于 cpp 文件中不需要导出的变量,常量或者函数,请使用匿名 namespace 封装或者用 static 修饰
      • 规则 6.1.1 不要在头文件中或者#include 之前使用 using 导入命名空间
    • 全局函数和静态成员函数
      • 建议 6.2.1 优先使用命名空间来管理全局函数,如果和某个 class 有直接关系的,可以使用静态成员函数
    • 全局常量和静态成员常量
      • 建议 6.3.1 优先使用命名空间来管理全局常量,如果和某个 class 有直接关系的,可以使用静态成员常量
    • 全局变量
      • 建议 6.4.1 尽量避免使用全局变量,考虑使用单例模式

    作用域

    命名空间

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

    建议 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();

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

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