Google开源项目风格指南缺点后置返回类型相对来说是非常新的语法,而且在C和Java中都没有相似的写法,因此可能对读者来说比较陌生,在已有的代码中有大量的函数声明,你不可能把它们都用新的语法重写一遍,因此实际的做法只能是使用旧的语法或者新旧混用.在这种情况下,只使用一种版本是相对来说更规整的形式,结论在大部分情况下,应当继续使用以往的函数声明写法,即将返回类型置于函数名前。只有在必需的时候(如Lambda表达式)或者使用后置语法能够简化书写并且提高易读性的时候才使用新的返回类型后置语法。但是后一种情况一般来说是很少见的,大部分时候都出现在相当复杂的模板代码中,而多数情况下不鼓励写这样复杂的模板代码2.65.来自Google的奇技Google用了很多自己实现的技巧/工具使C++代码更加健壮,我们使用C++的方式可能和你在其它地方见到的有所不同,2.6.15.1.所有权与智能指针>总述动态分配出的对象最好有单一且固定的所有主,并通过智能指针传递所有权>定义所有权是一种登记/管理动态内存和其它资源的技术.动态分配对象的所有主是一个对象或函数,后者负责确保当前者无用时就自动销毁前者,所有权有时可以共享,此时就由最后一个所有主来负责销毁它。甚至也可以不用共享,在代码中直接把所有权传递给其它对象.智能指针是一个通过重载*和->运算符以表现得如指针一样的类,智能指针类型被用来自动化所有权的登记工作,来确保执行销毁义务到位。std:unique_ptr是C++11新推出的一种智能指针类型,用来表示动态分配出的对象的独一无二的所有权;当std::unique_ptr离开作用域时,对象就会被销毁std::unique_ptr不能被复制,但可以把它移动(move)给新所有主,stdshared_ptr同样表示动态分配对象的所有权,但可以被共享,也可以被复制;对象的所有权由所有复制者共同拥有,最后一个复制者被销毁时,对象也会随着被销毁>优点·如果没有清晰、逻辑条理的所有权安排,不可能管理好动态分配的内存·传递对象的所有权,开销比复制来得小,如果可以复制的话·传递所有权也比”借用”指针或引用来得简单,毕竞它大大省去了两个用户一起协调对象生命周期的工作·如果所有权逻辑条理,有文档且不紧乱的话,可读性会有很大提升·可以不用手动完成所有权的登记工作,大大简化了代码,也免去了一大波错误之恼·对于const对象来说,智能指针简单易用,也比深度复制高效28Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 缺点 后置返回类型相对来说是非常新的语法, 而且在 C 和 Java 中都没有相似的写法, 因此可能对读者来说比 较陌生. 在已有的代码中有大量的函数声明, 你不可能把它们都用新的语法重写一遍. 因此实际的做法只能是使用 旧的语法或者新旧混用. 在这种情况下, 只使用一种版本是相对来说更规整的形式. 结论 在大部分情况下, 应当继续使用以往的函数声明写法, 即将返回类型置于函数名前. 只有在必需的时候 (如 Lambda 表达式) 或者使用后置语法能够简化书写并且提高易读性的时候才使用新的返回类型后置语 法. 但是后一种情况一般来说是很少见的, 大部分时候都出现在相当复杂的模板代码中, 而多数情况下不 鼓励写这样复杂的模板代码. 2.6 5. 来自 Google 的奇技 Google 用了很多自己实现的技巧 / 工具使 C++ 代码更加健壮, 我们使用 C++ 的方式可能和你在其它 地方见到的有所不同. 2.6.1 5.1. 所有权与智能指针 > 总述 动态分配出的对象最好有单一且固定的所有主, 并通过智能指针传递所有权. > 定义 所有权是一种登记/管理动态内存和其它资源的技术. 动态分配对象的所有主是一个对象或函数, 后者负 责确保当前者无用时就自动销毁前者. 所有权有时可以共享, 此时就由最后一个所有主来负责销毁它. 甚 至也可以不用共享, 在代码中直接把所有权传递给其它对象. 智能指针是一个通过重载 * 和 -> 运算符以表现得如指针一样的类. 智能指针类型被用来自动化所有 权的登记工作, 来确保执行销毁义务到位. std::unique_ptr 是 C++11 新推出的一种智能指针类型, 用 来表示动态分配出的对象的独一无二的所有权; 当 std::unique_ptr 离开作用域时, 对象就会被销毁. std::unique_ptr 不能被复制, 但可以把它移动(move)给新所有主. std::shared_ptr 同样表示动态分 配对象的所有权, 但可以被共享, 也可以被复制; 对象的所有权由所有复制者共同拥有, 最后一个复制者被 销毁时, 对象也会随着被销毁. > 优点 • 如果没有清晰、逻辑条理的所有权安排, 不可能管理好动态分配的内存. • 传递对象的所有权, 开销比复制来得小, 如果可以复制的话. • 传递所有权也比”借用”指针或引用来得简单, 毕竟它大大省去了两个用户一起协调对象生命周期 的工作. • 如果所有权逻辑条理, 有文档且不紊乱的话, 可读性会有很大提升. • 可以不用手动完成所有权的登记工作, 大大简化了代码, 也免去了一大波错误之恼. • 对于 const 对象来说, 智能指针简单易用, 也比深度复制高效. 28 Chapter 2. C++ 风格指南 - 内容目录
Google开源项目风格指南>缺点:不得不用指针(不管是智能的还是原生的)来表示和传递所有权指针语义可要比值语义复杂得许多了,特别是在API里:这时不光要操心所有权,还要顾及别名,生命周期,可变性以及其它大大小小的问题其实值语义的开销经常被高估,所以所有权传递带来的性能提升不一定能弥补可读性和复杂度的损失.·如果API依赖所有权的传递,就会害得客户端不得不用单一的内存管理模型·如果使用智能指针,那么资源释放发生的位置就会变得不那么明显,:std::unique_ptr的所有权传递原理是C++11的move语法,后者毕竟是刚刚推出的,容易迷惑程序员,·如果原本的所有权设计已经够完善了,那么若要引入所有权共享机制,可能不得不重构整个系统·所有权共享机制的登记工作在运行时进行,开销可能相当大,:某些极端情况下(例如循环引用),所有权被共享的对象永远不会被销毁·智能指针并不能够完全代替原生指针>结论如果必须使用动态分配,那么更倾向于将所有权保持在分配者手中。如果其他地方要使用这个对象,最好传递它的拷贝,或者传递一个不用改变所有权的指针或引用.倾向于使用std::unique_ptr来明确所有权传递,例如;std::unique_ptr<Foo>FooFactory();void FooConsumer(std::unique_ptr<Foo>ptr);如果没有很好的理由,则不要使用共享所有权,这里的理由可以是为了避免开销昂贵的拷贝操作,但是只有当性能提升非常明显,并且操作的对象是不可变的(比如说std::shared_ptr<constFoo>)时候,才能这么做.如果确实要使用共享所有权,建议于使用std::shared_ptr不要使用std::auto_ptr,使用std::unique_ptr代替它.2.6.2 5.2.Cpplint>总述使用cpplint.py检查风格错误.>说明cpplint.py是一个用来分析源文件,能检查出多种风格错误的工具。它不并完美,甚至还会漏报和误报但它仍然是一个非常有用的工具.在行尾加//NOLINT,或在上一行加//NOLINTNEXTLINE,可以忽略报错.某些项目会指导你如何使用他们的项目工具运行cpplint.py.如果你参与的项目没有提供,你可以单独下载cpplint.py292.6.5.来自Google的奇技
Google 开源项目风格指南 > 缺点 • 不得不用指针(不管是智能的还是原生的)来表示和传递所有权. 指针语义可要比值语义复杂得许 多了, 特别是在 API 里:这时不光要操心所有权, 还要顾及别名, 生命周期, 可变性以及其它大大小 小的问题. • 其实值语义的开销经常被高估, 所以所有权传递带来的性能提升不一定能弥补可读性和复杂度的损 失. • 如果 API 依赖所有权的传递, 就会害得客户端不得不用单一的内存管理模型. • 如果使用智能指针, 那么资源释放发生的位置就会变得不那么明显. • std::unique_ptr 的所有权传递原理是 C++11 的 move 语法, 后者毕竟是刚刚推出的, 容易迷惑 程序员. • 如果原本的所有权设计已经够完善了, 那么若要引入所有权共享机制, 可能不得不重构整个系统. • 所有权共享机制的登记工作在运行时进行, 开销可能相当大. • 某些极端情况下 (例如循环引用), 所有权被共享的对象永远不会被销毁. • 智能指针并不能够完全代替原生指针. > 结论 如果必须使用动态分配, 那么更倾向于将所有权保持在分配者手中. 如果其他地方要使用这个对象, 最好 传递它的拷贝, 或者传递一个不用改变所有权的指针或引用. 倾向于使用 std::unique_ptr 来明确所有 权传递, 例如: std::unique_ptr<Foo> FooFactory(); void FooConsumer(std::unique_ptr<Foo> ptr); 如果没有很好的理由, 则不要使用共享所有权. 这里的理由可以是为了避免开销昂贵的拷贝操作, 但是只 有当性能提升非常明显, 并且操作的对象是不可变的(比如说 std::shared_ptr<const Foo> )时候, 才 能这么做. 如果确实要使用共享所有权, 建议于使用 std::shared_ptr . 不要使用 std::auto_ptr, 使用 std::unique_ptr 代替它. 2.6.2 5.2. Cpplint > 总述 使用 cpplint.py 检查风格错误. > 说明 cpplint.py 是一个用来分析源文件, 能检查出多种风格错误的工具. 它不并完美, 甚至还会漏报和误报, 但它仍然是一个非常有用的工具. 在行尾加 // NOLINT, 或在上一行加 // NOLINTNEXTLINE, 可以忽略报 错. 某些项目会指导你如何使用他们的项目工具运行 cpplint.py. 如果你参与的项目没有提供, 你可以单独 下载 cpplint.py. 2.6. 5. 来自 Google 的奇技 29
Google开源项目风格指南2.6.3译者(acgtyrant)笔记1.把智能指针当成对象来看待的话,就很好领会它与所指对象之间的关系了2.原来Rust的Ownership思想是受到了C++智能指针的很大启发啊3.scoped_ptr和auto_ptr已过时.现在是shared_ptr和uniqued_ptr的天下了4.按本文来说,似乎除了智能指针,还有其它所有权机制,值得留意5.ArchLinux用户注意了,AUR有对cpplint打包,2.76.其他C++特性2.7.16.1.引用参数Tip:所有按引用传递的参数必须加上const.定义:在C语言中,如果函数需要修改变量的值,参数必须为指针,如intfoo(int*pval).在C++中,函数还可以声明引用参数:intfoo(int&val)优点:定义引用参数防止出现(*pva1)++这样丑陋的代码.像拷贝构造函数这样的应用也是必需的而且更明确,不接受NULL指针缺点:容易引起误解,因为引用在语法上是值变量却拥有指针的语义结论:函数参数列表中,所有引用参数都必须是const:void Foo(const string &in,string *out);事实上这在GoogleCode是一个硬性约定:输人参数是值参或const引用,输出参数为指针输入参数可以是const指针,但决不能是非const的引用参数,除非用于交换,比如swap()有时候,在输人形参中用constT*指针比constT&更明智。比如:·您会传null指针。·函数要把指针或对地址的引用赋值给输人形参。总之大多时候输人形参往往是constT&.若用constT*说明输入另有处理。所以若您要用constT*,则应有理有据,否则会害得读者误解。30Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 2.6.3 译者(acgtyrant)笔记 1. 把智能指针当成对象来看待的话, 就很好领会它与所指对象之间的关系了. 2. 原来 Rust 的 Ownership 思想是受到了 C++ 智能指针的很大启发啊. 3. scoped_ptr 和 auto_ptr 已过时. 现在是 shared_ptr 和 uniqued_ptr 的天下了. 4. 按本文来说, 似乎除了智能指针, 还有其它所有权机制, 值得留意. 5. Arch Linux 用户注意了, AUR 有对 cpplint 打包. 2.7 6. 其他 C++ 特性 2.7.1 6.1. 引用参数 Tip: 所有按引用传递的参数必须加上 const. 定义: 在 C 语言中, 如果函数需要修改变量的值, 参数必须为指针, 如 int foo(int *pval). 在 C++ 中, 函数还可以声明引用参数: int foo(int &val). 优点: 定义引用参数防止出现 (*pval)++ 这样丑陋的代码. 像拷贝构造函数这样的应用也是必需的. 而且更明确, 不接受 NULL 指针. 缺点: 容易引起误解, 因为引用在语法上是值变量却拥有指针的语义. 结论: 函数参数列表中, 所有引用参数都必须是 const: void Foo(const string &in, string *out); 事实上这在 Google Code 是一个硬性约定: 输入参数是值参或 const 引用, 输出参数为指针. 输入参数可以是 const 指针, 但决不能是非 const 的引用参数,除非用于交换,比如 swap(). 有时候,在输入形参中用 const T* 指针比 const T& 更明智。比如: • 您会传 null 指针。 • 函数要把指针或对地址的引用赋值给输入形参。 总之大多时候输入形参往往是 const T&. 若用 const T* 说明输入另有处理。所以若您要用 const T*, 则应有理有据,否则会害得读者误解。 30 Chapter 2. C++ 风格指南 - 内容目录
Google开源项目风格指南2.7.26.2.右值引用Tip:只在定义移动构造函数与移动赋值操作时使用右值引用.不要使用std::forward定义:右值引用是一种只能绑定到临时对象的引用的一种,其语法与传统的引用语法相似。例如,voidf(string&&s);声明了一个其参数是一个字符串的右值引用的函数优点:用于定义移动构造函数(使用类的右值引用进行构造的函数)使得移动一个值而非拷贝之成为可能.例如,如果v1是一个vector<string>,则autov2(std::move(v1))将很可能不再进行大量的数据复制而只是简单地进行指针操作,在某些情况下这将带来大幅度的性能提升右值引用使得编写通用的函数封装来转发其参数到另外一个函数成为可能,无论其参数是否是临时对象都能正常工作.右值引用能实现可移动但不可拷贝的类型,这一特性对那些在拷贝方面没有实际需求,但有时又需要将它们作为函数参数传递或塞入容器的类型很有用要高效率地使用某些标准库类型,例如std::unique_ptr,std::move是必需的缺点:右值引用是一个相对比较新的特性(由C++11引人),它尚未被广泛理解,类似引用崩溃,移动构造函数的自动推导这样的规则都是很复杂的结论:只在定义移动构造函数与移动赋值操作时使用右值引用,不要使用std::forward功能函数你可能会使用std::move来表示将值从一个对象移动而不是复制到另一个对象,2.7.36.3.函数重载Tip:若要用好函数重载,最好能让读者一看调用点(callsite)就胸有成竹,不用花心思猜测调用的重载函数到底是哪一种。该规则适用于构造函数。定义:你可以编写一个参数类型为conststring&的函数,然后用另一个参数类型为constchar*的函数重载它class Myclass (public:void Analyze(const string &text);void Analyze(const char *text, size_t textlen);F;312.7.6.其他C++特性
Google 开源项目风格指南 2.7.2 6.2. 右值引用 Tip: 只在定义移动构造函数与移动赋值操作时使用右值引用. 不要使用 std::forward. 定义: 右值引用是一种只能绑定到临时对象的引用的一种, 其语法与传统的引用语法相似. 例如, void f(string&& s); 声明了一个其参数是一个字符串的右值引用的函数. 优点: 用于定义移动构造函数 (使用类的右值引用进行构造的函数) 使得移动一个值而非拷贝之成 为可能. 例如, 如果 v1 是一个 vector<string>, 则 auto v2(std::move(v1)) 将很可能不再 进行大量的数据复制而只是简单地进行指针操作, 在某些情况下这将带来大幅度的性能提升. 右值引用使得编写通用的函数封装来转发其参数到另外一个函数成为可能, 无论其参数是否 是临时对象都能正常工作. 右值引用能实现可移动但不可拷贝的类型, 这一特性对那些在拷贝方面没有实际需求, 但有时 又需要将它们作为函数参数传递或塞入容器的类型很有用. 要高效率地使用某些标准库类型, 例如 std::unique_ptr, std::move 是必需的. 缺点: 右值引用是一个相对比较新的特性 (由 C++11 引入), 它尚未被广泛理解. 类似引用崩溃, 移 动构造函数的自动推导这样的规则都是很复杂的. 结论: 只在定义移动构造函数与移动赋值操作时使用右值引用, 不要使用 std::forward 功能函数. 你可能会使用 std::move 来表示将值从一个对象移动而不是复制到另一个对象. 2.7.3 6.3. 函数重载 Tip: 若要用好函数重载,最好能让读者一看调用点(call site)就胸有成竹,不用花心思猜测调用的重 载函数到底是哪一种。该规则适用于构造函数。 定义: 你可以编写一个参数类型为 const string& 的函数, 然后用另一个参数类型为 const char* 的函数重载它: class MyClass { public: void Analyze(const string &text); void Analyze(const char *text, size_t textlen); }; 2.7. 6. 其他 C++ 特性 31
Google开源项目风格指南优点:通过重载参数不同的同名函数,令代码更加直观。模板化代码需要重载,同时为使用者带来便利.缺点:如果函数单单靠不同的参数类型而重载(acgtyrant注:这意味着参数数量不变),读者就得十分熟悉C十十五花八门的匹配规则,以了解匹配过程具体到底如何。另外,当派生类只重载了某个函数的部分变体,继承语义容易令人困惑。结论:如果您打算重载一个函数,可以试试改在函数名里加上参数信息。例如,用AppendString()和AppendInt(等,而不是一口气重载多个Append()2.7.46.4.缺省参数Tip:我们不充许使用缺省函数参数,少数极端情况除外。尽可能改用函数重载,优点:当您有依赖缺省参数的函数时,您也许偶尔会修改修改这些缺省参数。通过缺省参数,不用再为个别情况而特意定义一大堆函数了。与函数重载相比,缺省参数语法更为清断,代码少,也很好地区分了必选参数」和「可选参数」。缺点:缺省参数会干扰函数指针,害得后者的函数签名(functionsignature)往往对不上所实际要调用的函数签名。即在一个现有函数添加缺省参数,就会改变它的类型,那么调用其地址的代码可能会出错,不过函数重载就没这问题了。此外,缺省参数会造成臃肿的代码,毕竟它们在每一个调用点(call site)都有重复(acgtyrant注:我猜可能是因为调用函数的代码表面上看来省去了不少参数,但编译器在编译时还是会在每一个调用代码里统统补上所有默认实参信息,造成大量的重复)。函数重载正好相反,毕竞它们所谓的「缺省参数」只会出现在函数定义里。结论:由于缺点并不是很严重,有些人依旧偏爱缺省参数胜于函数重载。所以除了以下情况,我们要求必须显式提供所有参数(acgtyrant注:即不能再通过缺省参数来省略参数了)。其一,位于:cc文件里的静态函数或匿名空间函数,毕竞都只能在局部文件里调用该函数了。其二,可以在构造函数里用缺省参数,毕竞不可能取得它们的地址。其三,可以用来模拟变长数组。//通过空AlphaNum以支持四个形参stringStrCat(constAlphaNum&a,constAlphaNum&b=gEmptyAlphaNum(continues on next page)32Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 优点: 通过重载参数不同的同名函数, 令代码更加直观. 模板化代码需要重载, 同时为使用者带来便 利. 缺点: 如果函数单单靠不同的参数类型而重载(acgtyrant 注:这意味着参数数量不变),读者就得 十分熟悉 C++ 五花八门的匹配规则,以了解匹配过程具体到底如何。另外,当派生类只重 载了某个函数的部分变体,继承语义容易令人困惑。 结论: 如果您打算重载一个函数, 可以试试改在函数名里加上参数信息。例如,用 AppendString() 和 AppendInt() 等,而不是一口气重载多个 Append(). 2.7.4 6.4. 缺省参数 Tip: 我们不允许使用缺省函数参数,少数极端情况除外。尽可能改用函数重载。 优点: 当您有依赖缺省参数的函数时,您也许偶尔会修改修改这些缺省参数。通过缺省参数,不用 再为个别情况而特意定义一大堆函数了。与函数重载相比,缺省参数语法更为清晰,代码少, 也很好地区分了「必选参数」和「可选参数」。 缺点: 缺省参数会干扰函数指针,害得后者的函数签名(function signature)往往对不上所实际要 调用的函数签名。即在一个现有函数添加缺省参数,就会改变它的类型,那么调用其地址的 代码可能会出错,不过函数重载就没这问题了。此外,缺省参数会造成臃肿的代码,毕竟它 们在每一个调用点(call site)都有重复(acgtyrant 注:我猜可能是因为调用函数的代码表面 上看来省去了不少参数,但编译器在编译时还是会在每一个调用代码里统统补上所有默认实 参信息,造成大量的重复)。函数重载正好相反,毕竟它们所谓的「缺省参数」只会出现在函 数定义里。 结论: 由于缺点并不是很严重,有些人依旧偏爱缺省参数胜于函数重载。所以除了以下情况,我们 要求必须显式提供所有参数(acgtyrant 注:即不能再通过缺省参数来省略参数了)。 其一,位于 .cc 文件里的静态函数或匿名空间函数,毕竟都只能在局部文件里调用该函数了。 其二,可以在构造函数里用缺省参数,毕竟不可能取得它们的地址。 其三,可以用来模拟变长数组。 // 通过空 AlphaNum 以支持四个形参 string StrCat(const AlphaNum &a, const AlphaNum &b = gEmptyAlphaNum, (continues on next page) 32 Chapter 2. C++ 风格指南 - 内容目录