当前位置:   article > 正文

Google 的 C++编程规范_google cpp 规范 常量

google cpp 规范 常量

1.1 头文件均使用 #define guards

#ifndef <PROJECT>_<PATH>_<FILE>_H_
#define <PROJECT>_<PATH>_<FILE>_H_

...

#endif  // <PROJECT>_<PATH>_<FILE>_H_
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

1.2 包含所有使用到的头文件

只要头文件中引用了其他文件中的符号,就需要包含该文件;(即使通过传递包含已经包含了该文件)

例如:foo.cc 应该包含 bar.h (即使 foo.h 中包含了 bar.h)

1.3 前向声明

一般使用前向声明的地方,都是使用指针(指针占用空间固定的,不用包含头文件);

但前向声明会隐藏类之间的关系(例如继承关系),这在匹配函数时就可能引发不同的行为;

1.4 内联函数不要超过10行

注意析构函数,因为隐式的成员变量和基类析构函数调用,析构函数往往比它们看起来的要长!

不要包括循环 或 switch

1.5 头文件顺序

(1)项目中的头文件应该带路径(src 目录下直到该文件的路径,不要使用 .. .

最直接关联头文件(例如在 foo.cc 文件中,此处为 foo.h,以下每类均用空行分隔)

C库

C++库

其他库

该项目其他头文件

(2)在每个模块,应该按字母顺序排序

2. 作用域

2.1 命名空间

(1)不要使用 using 语句;

不要使用 inline namespaces;(会自动把内部的标识符放到外层作用域)

在头文件之后,包含整个源文件;

(2)其名字中,字母全部小写,单词使用下划线分隔;避免缩写

Top-level namespace 应该基于项目名字;

(3)不要在头文件中使用命名空间别名,除非其作用域是局部的(例如在函数内部)

命名空间中内容不需要缩进;

2.2 匿名命名空间 (Internal Linkage)

不会被外部文件使用的变量,可以将它们放入匿名命名空间中,或声明为 static ,不要在 .h 文件中使用这两种方法;

(1)2.3 ~ 2.5 还需要进一步学习

2.3 函数和变量

(1)非成员函数尽量放到某个命名空间中;

(2)声明时就初始化变量

2.4 静态变量和全局变量

const 代表 read-only , 但这并不意味着它是不可变的,也不意味着值总是相同的。

常量随着程序的运行其值不会改变;

C++17 变量可以被声明为 inline,来保证只有一份;

2.5 线程变量

thread_local 变量实际是一些对象的集合,不同的线程实际上访问不同的对象。

在类或命名空间中必须使用编译时常量初始化;

3. 类

3.1 构造函数

构造函数中不要调用虚函数,不要抛出异常(可能失败的初始化操作);

3.2 避免隐式转换

不要定义隐式转换,必须加上 explicit 修饰单参数构造函数和转换操作符;(这将阻止 隐式转换和列表初始化语法)

3.3 拷贝/移动

一个可复制的类应该显式声明复制操作(即使使用 = default语法),一个仅移动的类应该明确声明移动操作,而一个不可复制/可移动的类则应该明确删除复制操作。

可复制的类也应该声明移动操作;

基类没有 private 部分,其可复制性/移动性由成员变量决定;
如果基类是不可复制/移动的,子类也是;

3.4 继承

尽可能使用组合关系,不得不使用继承的话,使用 public 继承;

虚函数最好都标注上 override 或 (less frequently) final

3.5 类成员变量都应该为 private,除非为常量;

3.6 命名规则

**全部小写,下划线区分单词; **

(1)类中成员变量最后有一个额外下划线;

(2)常量命名

const 或 constexpr 如果整个程序运行期间其值不变;以 k 开头;

(3)函数命名(类型名均采用这种方式 classes, structs, type aliases, enums)

每个单词首字母大写,不使用下划线

get 和 set 函数可以像变量一样命名

(4)枚举类型

enum class UrlTableError {	
  kOk = 0,							// 类似常量以 k 开头,每个首字母大写
  kOutOfMemory,
  kMalformedInput,
};
  • 1
  • 2
  • 3
  • 4
  • 5

3.7 注释

使用 空格 而非 Tab,可以设置 IDE 一个 Tab 改为2个空格

4. 函数

4.1 参数

仅输入作用的参数放在最前面;

(1)non-optional (必须传入的参数)

input parameters 使用 值传递 或 const 引用传递;

output and input/output parameters 使用引用传递;

(2)optional (可传可不传)

input parameters : 值传递时使用 std::optional

output and input/output parameters :使用 non-const 指针;

4.2 尽量小的函数

最好不要超过 40 行,如果超过拆分成小函数;

4.3 类型转换

除非转换为 void,否则都使用 cast ;

数值类型,使用int64_t{x}

intptr_t 指针大小的整数

char 类型的空使用 ‘\0’ ;

4.4 类型推导

如果使得代码更清晰或安全,才会使用。

例如在 auto ptr = make_shared<xxx>

4.5 switch

应该总是设置 default

凡是需要顺序往下执行的(不是执行完 case 后就 break 的情况),需要添加 [[fallthrough]];

二、现代C++

1. 类型推导

1.1 模板类型推导

template<typename T>
void f(ParamType param);

...

f(expr);   //从expr中推导T和ParamType
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

基本原则:

(1)一般情况下,expr 与 T 的类型是一致的,T 一般不为引用,除非在下述第二种情况下;

(2)在模板类型推导时,有引用的实参会被视为无引用,他们的引用会被忽略

1.1.1 ParamType 是一个指针或引用,但不是通用引用

(1)ParamType 是 引用

  1. 如果 expr 的类型是一个引用,忽略引用部分
  2. 然后 expr 的类型与 ParamType 进行模式匹配来决定 T (相同的会消掉)

expr 若是指针,则仍按基本规则匹配

void f(const T& param);  这里 const 表明的是 T 本身是不可变的,若传递为 int* 指针,param 类型为  int (*const)&
  • 1

(2)ParamType 是 指针,与上述类似

  1. 如果 expr 的类型是一个指针,忽略指针部分
  2. 与上述类似 。。。

expr 不能是引用;

1.1.2 ParamType 是一个通用引用
  1. 如果 expr 是左值,T 和 ParamType 都会被推导为左值引用。这非常不寻常,第一,这是模板类型推导中唯一一种T被推导为引用的情况。第二,虽然 ParamType 被声明为右值引用类型,但是最后推导的结果是左值引用。

  2. 如果 expr 是右值,就使用基本的推导规则

1.1.3 ParamType 既不是指针也不是引用

按通过传值(pass-by-value)的方式处理

  1. 如果 expr 的类型是一个引用,忽略这个引用部分;忽略 对象自身的 const/ volatile
  2. 如果 expr 的类型是一个指针,按基本规则;
1.1.4 数组和函数实参

数组和函数实参会退化为指针

void func(const char (param)[13]) ;
等价于 void func(const char *param) ;

void func(const char (&param)[13]);  可以接受指向数组的引用,必须得 const char[13] 数组类型才能调用
  • 1
  • 2
  • 3
  • 4

有指向函数的引用;

template<typename T>
void f2(T & param);                 //传引用给f2
f2(someFunc);                       //param被推导为指向函数的引用,
                                    //类型是void(&)(int, double)
  • 1
  • 2
  • 3
  • 4

2. auto类型推导

(1)auto类型推导 与 模板类型推导基本一致;可将 auto 视为 T,类型说明符视为 ParamType ,等号右边值视为实参;

const auto & x  = 27; 类型说明符为 const auto &
  • 1

(2)特例:使用大括号初始化的变量,auto 推导出的类型为 std::initializer_list ( 大括号里面必须是相同类型的变量),模板无法推导这种情况,除非显式写为

template<typename T>
void f(std::initializer_list<T> initList);


auto a{42};  C++17 现在推导出为 int
auto a{42, 17};  C++17 中为 error
auto c = {42}; // still initializes a std::initializer_list<int>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

(3)auto 可用于函数返回值或 lambda 表达式中的形参,此时其完全等价于 模板类型推导

lambda 表达式的返回值是通过 auto 规则推导。此时其完全等价于 模板类型推导

(4)lambda 中初始化捕获器中的类型推导也采用了 auto 规则,此时是真正的 auto 推导;

初始化捕获器中实际上声明了新的变量;

特列:[&] 代表引用捕获,需要注意引用变量的生命期要比 lambda 长,否则造成悬挂指针的危险;

(5)使用 auto 可以避免未初始化的无效变量,省略冗长的声明类型,直接保存闭包

比如在遍历容器时使用 auto,注意:unordered_map 的 key 是 const 类型;

不可见的代理类通常不适用于 auto,此时可以使用 显式类型初始器惯用法强制 auto 推导出你想要的结果

auto highPriority = static_cast<bool>(features(w)[5]);
  • 1

3. decltype

尾置返回类型的好处是我们可以在函数返回类型中使用函数形参相关的信息(这种情况下不依赖 auto 推导)

decltype 应用于变量名,只是简单返回变量的声明类型;
decltype 应用于左值表达式,返回相应左值引用;
语法 decltype(auto) 表示需要推导出类型,但是它使用 decltype 的规则进行推导

如果想表达空指针时,模板类型推导将 0 和 NULL 推导为一个错误的类型(int 和 整型),最好使用 nullptr;

4. 统一初始化

(1)大括号初始化能被用于为非静态数据成员指定默认初始值(不能使用 小括号 初始化),不允许内置类型间隐式的变窄转换

不可拷贝的对象的初始化(不能使用 = 初始化)

创建默认构造类时,不要使用下述方式
Widget w3();                    //最令人头疼的解析!声明一个函数
  • 1
  • 2

(2)问题:

大括号初始化类在调用构造函数时,如果有一个或者多个构造函数的声明包含一个 std::initializer_list 形参,那么使用括号初始化语法的调用更倾向于选择带 std::initializer_list 的那个构造函数,即使另一个非 std::initializer_list 构造函数和实参更匹配

5. 限域枚举

限域 enum 的枚举名仅在 enum 内可见。要转换为其它类型只能使用 cast。

非限域/限域 enum 都支持底层类型说明语法,限域 enum 底层类型默认是 int。非限域 enum 没有默认底层类型。可以使用 std::underlying_type 获得其底层类型;

需要隐式转换时(例如,枚举结合tuple使用时),可以使用非限域枚举;

6. 上下文关键字

只是位于特定上下文才被视为关键字

(1)给派生类重写函数全都加上 override ;

(2)虚函数添加 final 可以防止派生类重写。final 也能用于类,这时这个类不能用作基类

7. 别名声明

using UPtrMapSS =
    std::unique_ptr<std::unordered_map<std::string, std::string>>;
  • 1
  • 2

(1)别名模板:别名声明可以被模板化;但是 typedef 不能,只能把 typedef 嵌套进模板化的 struct 才能表达相同的事情;

template<typename T>                            //MyAllocList<T>是
using MyAllocList = std::list<T, MyAlloc<T>>;   //std::list<T, MyAlloc<T>>
                                                //的同义词

MyAllocList<Widget> lw;                         //用户代码

// typedef 做法
template<typename T>                            //MyAllocList<T>是
struct MyAllocList {                            //std::list<T, MyAlloc<T>>
    typedef std::list<T, MyAlloc<T>> type;      //的同义词  
};

MyAllocList<Widget>::type lw;                   //用户代码
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

(2)C++ 14 中使用别名模板将 type trait 改为如下形式

std::remove_reference_t
  • 1

8. constexpr

constexpr 表明一个值不仅仅是常量,还是编译期可知的

整数模板参数(包括std::array对象的长度),枚举名的值,对齐修饰符(译注:alignas(val))需要编译期可知的值;

当传递编译期可知的值时,constexpr 函数可以产出编译期可知的结果;当一个 constexpr 函数被一个或者多个编译期不可知值调用时,它就像普通函数一样;

9. const 成员函数

const 成员函数,那就表示着它是一个读操作,因此需要提供线程安全性;

使用 std::atomic 变量可能比互斥量提供更好的性能,但是它只适合操作单个变量或内存位置

10. 默认函数

这些默认生成函数只有被用到时才会生成;让编译器默认生成时,也可以写在 .cc 文件中,如下所示:

Widget::~Widget() = default; 
  • 1

(1)两个拷贝操作是独立的:声明一个不会限制编译器生成另一个。

(2)两个移动操作不是相互独立的。如果你声明了其中一个,编译器就不再生成另一个。

(3)如果一个类显式声明了拷贝操作,编译器就不会生成移动操作。

声明移动操作(构造或赋值)使得编译器禁用拷贝操作(禁用的是编译器生成的拷贝函数,而非用户显式声明的)。编译器通过给拷贝操作加上delete来保证。

(4)Rule of Three 规则。这个规则告诉我们如果你声明了拷贝构造函数,拷贝赋值运算符,或者析构函数三者之一,你应该也声明其余两个;

如果声明自身析构函数,编译器就不会生成移动操作

成员函数模板不抑制特殊成员函数的生成。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小丑西瓜9/article/detail/395677
推荐阅读
相关标签
  

闽ICP备14008679号