当前位置:   article > 正文

Google C++ Style文档及常用代码规范(三):来自 Google 的奇技、其他 C++ 特性、规则特例、结束语_google c++代码规范

google c++代码规范

Google C++ Style文档及常用代码规范(三):来自 Google 的奇技、其他 C++ 特性、规则特例、结束语

英文原版:https://google.github.io/styleguide/cppguide.html
GitHub仓库:https://github.com/google/styleguide

中文翻译:https://zh-google-styleguide.readthedocs.io/en/latest/
GitHub仓库:https://github.com/zh-google-styleguide/zh-google-styleguide

以下是从中文翻译版中截取了一部分常用内容,并附上了原文链接:

来自 Google 的奇技

所有权与智能指针

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/magic/#section-1

动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权.

如果必须使用动态分配, 那么更倾向于将所有权保持在分配者手中. 如果其他地方要使用这个对象, 最好传递它的拷贝, 或者传递一个不用改变所有权的指针或引用. 倾向于使用 std::unique_ptr 来明确所有权传递, 例如:

std::unique_ptr<Foo> FooFactory();
void FooConsumer(std::unique_ptr<Foo> ptr);
  • 1
  • 2

如果没有很好的理由, 则不要使用共享所有权. 这里的理由可以是为了避免开销昂贵的拷贝操作, 但是只有当性能提升非常明显, 并且操作的对象是不可变的(比如说 std::shared_ptr<const Foo> )时候, 才能这么做. 如果确实要使用共享所有权, 建议于使用 std::shared_ptr .

不要使用 std::auto_ptr, 使用 std::unique_ptr 代替它.

Cpplint

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/magic/#cpplint

使用 cpplint.py 检查风格错误.

cpplint.py 是一个用来分析源文件, 能检查出多种风格错误的工具. 它不并完美, 甚至还会漏报和误报, 但它仍然是一个非常有用的工具. 在行尾加 // NOLINT, 或在上一行加 // NOLINTNEXTLINE, 可以忽略报错.

某些项目会指导你如何使用他们的项目工具运行 cpplint.py. 如果你参与的项目没有提供, 你可以单独下载 cpplint.py.

其他 C++ 特性

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/

右值引用

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-2

一文读懂C++右值引用和std::move

用于定义移动构造函数 (使用类的右值引用进行构造的函数) 使得移动一个值而非拷贝之成为可能. 例如, 如果 v1 是一个 vector<string>, 则 auto v2(std::move(v1)) 将很可能不再进行大量的数据复制而只是简单地进行指针操作, 在某些情况下这将带来大幅度的性能提升.

只在定义 移动构造函数移动赋值操作时使用右值引用, 不要使用 std::forward 功能函数. 你可能会使用 std::move 来表示将值从一个对象移动而不是复制到另一个对象.

变长数组和 alloca()

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#alloca

不允许使用变长数组和 alloca().

变长数组和 alloca() 不是标准 C++ 的组成部分. 更重要的是, 它们根据数据大小动态分配堆栈内存, 会引起难以发现的内存越界 bugs: “在我的机器上运行的好好的, 发布后却莫名其妙的挂掉了”.

改用更安全的分配器(allocator),就像 std::vectorstd::unique_ptr<T[]>.

异常

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-6

Google 现有的大多数 C++ 代码都没有异常处理。Google 禁止使用异常这一点, 仅仅是为了自身的方便, 说大了, 无非是基于软件管理成本上, 实际使用中还是自己决定。

运行时类型识别

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-7

禁止使用 RTTI.

RTTI 有合理的用途但是容易被滥用, 因此在使用时请务必注意. 在单元测试中可以使用 RTTI, 但是在其他代码中请尽量避免. 尤其是在新代码中, 使用 RTTI 前务必三思. 如果你的代码需要根据不同的对象类型执行不同的行为的话, 请考虑用以下的两种替代方案之一查询类型:

虚函数可以根据子类类型的不同而执行不同代码. 这是把工作交给了对象本身去处理.

如果这一工作需要在对象之外完成, 可以考虑使用双重分发的方案, 例如使用访问者设计模式. 这就能够在对象之外进行类型判断.

如果程序能够保证给定的基类实例实际上都是某个派生类的实例, 那么就可以自由使用 dynamic_cast. 在这种情况下, 使用 dynamic_cast 也是一种替代方案.

基于类型的判断树是一个很强的暗示, 它说明你的代码已经偏离正轨了. 不要像下面这样:

if (typeid(*data) == typeid(D1)) {
  ...
} else if (typeid(*data) == typeid(D2)) {
  ...
} else if (typeid(*data) == typeid(D3)) {
...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

类型转换

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-8

使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)xint y = int(x) 等转换方式;

不要使用 C 风格类型转换. 而应该使用 C++ 风格.

  • static_cast 替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针时.
  • const_cast 去掉 const 限定符.
  • reinterpret_cast 指针类型和整型或其它指针之间进行不安全的相互转换. 仅在你对所做一切了然于心时使用.

至于 dynamic_cast 参见 6.8. 运行时类型识别.

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-9

只在记录日志时使用流.

流使得 pread() 等功能函数很难执行. 如果不使用 printf 风格的格式化字符串, 某些格式化操作 (尤其是常用的格式字符串 %.*s) 用流处理性能是很低的. 流不支持字符串操作符重新排序 (%1s), 而这一点对于软件国际化很有用.

前置自增和自减

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-10

对于迭代器和其他模板对象使用前缀形式 (++i) 的自增, 自减运算符.

不考虑返回值的话, 前置自增 ( ++i) 通常要比后置自增 ( i++) 效率更高. 因为后置自增 (或自减) 需要对表达式的值 i 进行一次拷贝. 如果 i 是迭代器或其他非数值类型, 拷贝的代价是比较大的. 既然两种自增方式实现的功能一样, 为什么不总是使用前置自增呢?

const用法

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#const

在声明的变量或参数前加上关键字 const 用于指明变量值不可被篡改 (如 const int foo ). 为类中的函数加上 const 限定符表明该函数不会修改类成员变量的状态 (如 class Foo { int Bar(char c) const; };).

const 变量, 数据成员, 函数和参数为编译时类型检测增加了一层保障; 便于尽早发现错误. 因此, 我们强烈建议在任何可能的情况下使用 const:

  • 如果函数不会修改你传入的引用或指针类型参数, 该参数应声明为 const.
  • 尽可能将函数声明为 const. 访问函数应该总是 const. 其他不会修改任何数据成员, 未调用非 const 函数, 不会返回数据成员非 const 指针或引用的函数也应该声明成 const.
  • 如果数据成员在对象构造之后不再发生变化, 可将其定义为 const.

然而, 也不要发了疯似的使用 const. 像 const int * const * const x; 就有些过了, 虽然它非常精确的描述了常量 x. 关注真正有帮助意义的信息: 前面的例子写成 const int** x 就够了.

关键字 mutable 可以使用, 但是在多线程中是不安全的, 使用时首先要考虑线程安全.

constexpr 用法

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#constexpr

在 C++11 里,用 constexpr 来定义真正的常量,或实现常量初始化。

变量可以被声明成 constexpr 以表示它是真正意义上的常量,即在编译时和运行时都不变。函数构造函数也可以被声明成 constexpr, 以用来定义 constexpr 变量。

整型

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-11

C++ 内建整型中, 仅使用 int. 如果程序中需要不同大小的变量, 可以使用 <stdint.h> 中长度精确的整型, 如 int16_t.如果您的变量可能不小于 2^31 (2GiB), 就用 64 位变量比如 int64_t. 此外要留意,哪怕您的值并不会超出 int 所能够表示的范围,在计算过程中也可能会溢出。所以拿不准时,干脆用更大的类型。

C++ 没有指定整型的大小. 通常人们假定 short 是 16 位, int 是 32 位, long 是 32 位, long long 是 64 位.

<stdint.h> 定义了 int16_t, uint32_t, int64_t 等整型, 在需要确保整型大小时可以使用它们代替 short, unsigned long long 等. 在 C 整型中, 只使用 int. 在合适的情况下, 推荐使用标准类型如 size_tptrdiff_t.

如果已知整数不会太大, 我们常常会使用 int, 如循环计数. 在类似的情况下使用原生类型 int. 你可以认为 int 至少为 32 位, 但不要认为它会多于 32 位. 如果需要 64 位整型, 用 int64_tuint64_t.

对于大整数, 使用 int64_t.

不要使用 uint32_t 等无符号整型, 除非你是在表示一个位组而不是一个数值, 或是你需要定义二进制补码溢出. 尤其是不要为了指出数值永不会为负, 而使用无符号类型. 相反, 你应该使用断言来保护数据.

如果您的代码涉及容器返回的大小(size),确保其类型足以应付容器各种可能的用法。拿不准时,类型越大越好。

小心整型类型转换和整型提升(acgtyrant 注:integer promotions, 比如 intunsigned int 运算时,前者被提升为 unsigned int 而有可能溢出),总有意想不到的后果。

预处理宏

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-13

使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之.

下面给出的用法模式可以避免使用宏带来的问题; 如果你要宏, 尽可能遵守:

  • 不要在 .h 文件中定义宏.
  • 在马上要使用时才进行 #define, 使用后要立即 #undef.
  • 不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;
  • 不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为.
  • 不要用 ## 处理函数,类和变量的名字。

0, nullptr 和 NULL

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#nullptr-null

整数0, 实数0.0, 指针nullptrNULL, 字符 (串) '\0'.

对于指针 (地址值), 到底是用 0, NULL 还是 nullptr. C++11 项目用 nullptr; C++03 项目则用 NULL, 毕竟它看起来像指针。实际上,一些 C++ 编译器对 NULL 的定义比较特殊,可以输出有用的警告,特别是 sizeof(NULL) 就和 sizeof(0) 不一样。

字符 (串) 用 '\0', 不仅类型正确而且可读性好.

sizeof

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#sizeof

尽可能用 sizeof(varname) 代替 sizeof(type).

使用 sizeof(varname) 是因为当代码中变量类型改变时会自动更新. 您或许会用 sizeof(type) 处理不涉及任何变量的代码,比如处理来自外部或内部的数据格式,这时用变量就不合适了。

auto

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#auto

auto 绕过烦琐的类型名,只要可读性好就继续用,别用在局部变量之外的地方。

auto 只能用在局部变量里用。别用在文件作用域变量,命名空间作用域变量和类数据成员里。永远别列表初始化 auto 变量。

auto 和 C++11 列表初始化的合体令人摸不着头脑:

auto x(3);  // 圆括号。
auto y{3};  // 大括号。
  • 1
  • 2

它们不是同一回事——xint, y 则是 std::initializer_list<int>. 其它一般不可见的代理类型(acgtyrant 注:normally-invisible proxy types, 它涉及到 C++ 鲜为人知的坑:Why is vector<bool> not a STL container?)也有大同小异的陷阱。

列表初始化

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-14

用户自定义类型也可以定义接收 std::initializer_list<T> 的构造函数和赋值运算符,以自动列表初始化:

class MyType {
 public:
  // std::initializer_list 专门接收 init 列表。
  // 得以值传递。
  MyType(std::initializer_list<int> init_list) {
    for (int i : init_list) append(i);
  }
  MyType& operator=(std::initializer_list<int> init_list) {
    clear();
    for (int i : init_list) append(i);
  }
};
MyType m{2, 3, 5, 7};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

列表初始化也适用于常规数据类型的构造,哪怕没有接收 std::initializer_list<T> 的构造函数。

double d{1.23};
// MyOtherType 没有 std::initializer_list 构造函数,
 // 直接上接收常规类型的构造函数。
class MyOtherType {
 public:
  explicit MyOtherType(string);
  MyOtherType(int, string);
};
MyOtherType m = {1, "b"};
// 不过如果构造函数是显式的(explict),您就不能用 `= {}` 了。
MyOtherType m{"b"};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

千万别直接列表初始化 auto 变量,看下一句,估计没人看得懂:

auto d = {1.23};        // d 即是 std::initializer_list<double>
  • 1
auto d = double{1.23};  // 善哉 -- d 即为 double, 并非 std::initializer_list.
  • 1

至于格式化,参见9.7. 列表初始化格式

Lambda 表达式

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#lambda

适当使用 lambda 表达式。别用默认 lambda 捕获,所有捕获都要显式写出来。

  • 按 format 小用 lambda 表达式怡情。
  • 禁用默认捕获,捕获都要显式写出来。打比方,比起 [=](int x) {return x + n;}, 您该写成 [n](int x) {return x + n;} 才对,这样读者也好一眼看出 n 是被捕获的值。
  • 匿名函数始终要简短,如果函数体超过了五行,那么还不如起名(acgtyrant 注:即把 lambda 表达式赋值给对象),或改用函数。
  • 如果可读性更好,就显式写出 lambd 的尾置返回类型,就像auto.

模板编程

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#section-15

不要使用复杂的模板编程

  • 模板编程有时候能够实现更简洁更易用的接口, 但是更多的时候却适得其反. 因此模板编程最好只用在少量的基础组件, 基础数据结构上, 因为模板带来的额外的维护成本会被大量的使用给分担掉
  • 在使用模板编程或者其他复杂的模板技巧的时候, 你一定要再三考虑一下. 考虑一下你们团队成员的平均水平是否能够读懂并且能够维护你写的模板代码.或者一个非c++ 程序员和一些只是在出错的时候偶尔看一下代码的人能够读懂这些错误信息或者能够跟踪函数的调用流程. 如果你使用递归的模板实例化, 或者类型列表, 或者元函数, 又或者表达式模板, 或者依赖SFINAE, 或者sizeof 的trick 手段来检查函数是否重载, 那么这说明你模板用的太多了, 这些模板太复杂了, 我们不推荐使用
  • 如果你使用模板编程, 你必须考虑尽可能的把复杂度最小化, 并且尽量不要让模板对外暴露. 你最好只在实现里面使用模板, 然后给用户暴露的接口里面并不使用模板, 这样能提高你的接口的可读性. 并且你应该在这些使用模板的代码上写尽可能详细的注释. 你的注释里面应该详细的包含这些代码是怎么用的, 这些模板生成出来的代码大概是什么样子的. 还需要额外注意在用户错误使用你的模板代码的时候需要输出更人性化的出错信息. 因为这些出错信息也是你的接口的一部分, 所以你的代码必须调整到这些错误信息在用户看起来应该是非常容易理解, 并且用户很容易知道如何修改这些错误

Boost 库

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/others/#boost-1

只使用 Boost 中被认可的库.

Boost 库集 是一个广受欢迎, 经过同行鉴定, 免费开源的 C++ 库集.

为了向阅读和维护代码的人员提供更好的可读性, 我们只允许使用 Boost 一部分经认可的特性子集. 目前允许使用以下库:

  • Call Traits : boost/call_traits.hpp
  • Compressed Pair : boost/compressed_pair.hpp
  • <The Boost Graph Library (BGL) : boost/graph, except serialization (adj_list_serialize.hpp) and parallel/distributed algorithms and data structures(boost/graph/parallel/* and boost/graph/distributed/*)
  • Property Map : boost/property_map.hpp
  • The part of Iterator that deals with defining iterators: boost/iterator/iterator_adaptor.hpp, boost/iterator/iterator_facade.hpp, and boost/function_output_iterator.hpp
  • The part of Polygon that deals with Voronoi diagram construction and doesn’t depend on the rest of Polygon: boost/polygon/voronoi_builder.hpp, boost/polygon/voronoi_diagram.hpp, and boost/polygon/voronoi_geometry_type.hpp
  • Bimap : boost/bimap
  • Statistical Distributions and Functions : boost/math/distributions
  • Multi-index : boost/multi_index
  • Heap : boost/heap
  • The flat containers from Container: boost/container/flat_map, and boost/container/flat_set

我们正在积极考虑增加其它 Boost 特性, 所以列表中的规则将不断变化.

以下库可以用,但由于如今已经被 C++ 11 标准库取代,不再鼓励:

规则特例

前面说明的编程习惯基本都是强制性的. 但所有优秀的规则都允许例外, 这里就是探讨这些特例.

现有不合规范的代码

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/exceptions/#section-2

对于现有不符合既定编程风格的代码可以网开一面.

当你修改使用其他风格的代码时, 为了与代码原有风格保持一致可以不使用本指南约定. 如果不放心, 可以与代码原作者或现在的负责人员商讨. 记住, 一致性也包括原有的一致性.

Windows 代码

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/exceptions/#windows

总述

Windows 程序员有自己的编程习惯, 主要源于 Windows 头文件和其它 Microsoft 代码. 我们希望任何人都可以顺利读懂你的代码, 所以针对所有平台的 C++ 编程只给出一个单独的指南.

说明

如果你习惯使用 Windows 编码风格, 这儿有必要重申一下某些你可能会忘记的指南:

  • 不要使用匈牙利命名法 (比如把整型变量命名成 iNum). 使用 Google 命名约定, 包括对源文件使用 .cc 扩展名.
  • Windows 定义了很多原生类型的同义词 (YuleFox 注: 这一点, 我也很反感), 如 DWORD, HANDLE 等等. 在调用 Windows API 时这是完全可以接受甚至鼓励的. 即使如此, 还是尽量使用原有的 C++ 类型, 例如使用 const TCHAR * 而不是 LPCTSTR.
  • 使用 Microsoft Visual C++ 进行编译时, 将警告级别设置为 3 或更高, 并将所有警告(warnings)当作错误(errors)处理.
  • 不要使用 #pragma once; 而应该使用 Google 的头文件保护规则. 头文件保护的路径应该相对于项目根目录 (Yang.Y 注: 如 #ifndef SRC_DIR_BAR_H_, 参考 #define 保护 一节).
  • 除非万不得已, 不要使用任何非标准的扩展, 如 #pragma__declspec. 使用 __declspec(dllimport)__declspec(dllexport) 是允许的, 但必须通过宏来使用, 比如 DLLIMPORTDLLEXPORT, 这样其他人在分享使用这些代码时可以很容易地禁用这些扩展.

然而, 在 Windows 上仍然有一些我们偶尔需要违反的规则:

  • 通常我们 禁止使用多重继承, 但在使用 COM 和 ATL/WTL 类时可以使用多重继承. 为了实现 COM 或 ATL/WTL 类/接口, 你可能不得不使用多重实现继承.
  • 虽然代码中不应该使用异常, 但是在 ATL 和部分 STL(包括 Visual C++ 的 STL) 中异常被广泛使用. 使用 ATL 时, 应定义 _ATL_NO_EXCEPTIONS 以禁用异常. 你需要研究一下是否能够禁用 STL 的异常, 如果无法禁用, 可以启用编译器异常. (注意这只是为了编译 STL, 自己的代码里仍然不应当包含异常处理).
  • 通常为了利用头文件预编译, 每个每个源文件的开头都会包含一个名为 StdAfx.hprecompile.h 的文件. 为了使代码方便与其他项目共享, 请避免显式包含此文件 (除了在 precompile.cc 中), 使用 /FI 编译器选项以自动包含该文件.
  • 资源头文件通常命名为 resource.h 且只包含宏, 这一文件不需要遵守本风格指南.

结束语

https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/end/

运用常识和判断力, 并且 保持一致.

编辑代码时, 花点时间看看项目中的其它代码, 并熟悉其风格. 如果其它代码中 if 语句使用空格, 那么你也要使用. 如果其中的注释用星号 (*) 围成一个盒子状, 那么你同样要这么做.

风格指南的重点在于提供一个通用的编程规范, 这样大家可以把精力集中在实现内容而不是表现形式上. 我们展示的是一个总体的的风格规范, 但局部风格也很重要, 如果你在一个文件中新加的代码和原有代码风格相去甚远, 这就破坏了文件本身的整体美观, 也让打乱读者在阅读代码时的节奏, 所以要尽量避免.

好了, 关于编码风格写的够多了; 代码本身才更有趣. 尽情享受吧!

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

闽ICP备14008679号