Google开源项目风格指南(continuedfrompreviouspage)constAlphaNum&c=gEmptyAlphaNum,const AlphaNum &d = gEmptyAlphaNum);2.7.56.5.变长数组和alloca0)Tip:我们不允许使用变长数组和alloca()优点:变长数组具有浑然天成的语法.变长数组和al1oca()也都很高效缺点:变长数组和al1oca()不是标准C++的组成部分.更重要的是,它们根据数据大小动态分配堆栈内存,会引起难以发现的内存越界bugs:“在我的机器上运行的好好的,发布后却莫名其妙的挂掉了”结论:改用更安全的分配器(allocator),就像std::vector或std::unique_ptr<T>2.7.66.6.友元Tip:我们允许合理的使用友元类及友元函数通常友元应该定义在同一文件内,避免代码读者跑到其它文件查找使用该私有成员的类.经常用到友元的一个地方是将FooBuilder声明为Foo的友元,以便FooBuilder正确构造Foo的内部状态,而无需将该状态暴露出来.某些情况下,将一个单元测试类声明成待测类的友元会很方便友元扩大了(但没有打破)类的封装边界.某些情况下,相对于将类成员声明为public,使用友元是更好的选择,尤其是如果你只允许另一个类访间该类的私有成员时当然,大多数类都只应该通过其提供的公有成员进行互操作2.7.76.7.异常Tip:我们不使用C++异常优点:·异常允许应用高层决定如何处理在底层嵌套函数中「不可能发生」的失败(failures),不用管那些含糊且容易出错的错误代码(acgtyrant注:errorcode,我猜是C语言函数返回的非零int值)。332.7.6.其他C++特性
Google 开源项目风格指南 (continued from previous page) const AlphaNum &c = gEmptyAlphaNum, const AlphaNum &d = gEmptyAlphaNum); 2.7.5 6.5. 变长数组和 alloca() Tip: 我们不允许使用变长数组和 alloca(). 优点: 变长数组具有浑然天成的语法. 变长数组和 alloca() 也都很高效. 缺点: 变长数组和 alloca() 不是标准 C++ 的组成部分. 更重要的是, 它们根据数据大小动态分配 堆栈内存, 会引起难以发现的内存越界 bugs: “在我的机器上运行的好好的, 发布后却莫名其 妙的挂掉了”. 结论: 改用更安全的分配器(allocator),就像 std::vector 或 std::unique_ptr<T[]>. 2.7.6 6.6. 友元 Tip: 我们允许合理的使用友元类及友元函数. 通常友元应该定义在同一文件内, 避免代码读者跑到其它文件查找使用该私有成员的类. 经常用到友元的 一个地方是将 FooBuilder 声明为 Foo 的友元, 以便 FooBuilder 正确构造 Foo 的内部状态, 而无需将该 状态暴露出来. 某些情况下, 将一个单元测试类声明成待测类的友元会很方便. 友元扩大了 (但没有打破) 类的封装边界. 某些情况下, 相对于将类成员声明为 public, 使用友元是更好 的选择, 尤其是如果你只允许另一个类访问该类的私有成员时. 当然, 大多数类都只应该通过其提供的公 有成员进行互操作. 2.7.7 6.7. 异常 Tip: 我们不使用 C++ 异常. 优点: • 异常允许应用高层决定如何处理在底层嵌套函数中「不可能发生」的失败(failures),不用管那些 含糊且容易出错的错误代码(acgtyrant 注:error code, 我猜是C语言函数返回的非零 int 值)。 2.7. 6. 其他 C++ 特性 33
Google开源项目风格指南·很多现代语言都用异常。引入异常使得C++与Python,Java以及其它类C++的语言更一脉相承。·有些第三方C++库依赖异常,禁用异常就不好用了。·异常是处理构造函数失败的唯一途径。虽然可以用工厂函数(acgtyrant注:factoryfunction,出自C++的一种设计模式,即「简单工厂模式J)或Init)方法代替异常,但是前者要求在堆栈分配内存,后者会导致刚创建的实例处于”无效“状态。·在测试框架里很好用。缺点:·在现有函数中添加throw语句时,您必须检查所有调用点。要么让所有调用点统统具备最低限度的异常安全保证,要么眼静静地看异常一路欢快地往上跑,最终中断掉整个程序。举例,f()调用g),g()又调用h(),且h抛出的异常被f捕获。当心g,否则会没妥善清理好。·还有更常见的,异常会彻底扰乱程序的执行流程并难以判断:函数也许会在您意料不到的地方返回。您或许会加一大堆何时何处处理异常的规定来降低风险,然而开发者的记忆负担更重了。·异常安全需要RAII和不同的编码实践。要轻松编写出正确的异常安全代码需要大量的支持机制更进一步地说,为了避免读者理解整个调用表,异常安全必须隔绝从持续状态写到“提交”状态的逻辑这一点有利有弊(因为你也许不得不为了隔离提交而混淆代码),如果允许使用异常,我们就不得不时刻关注这样的弊端,即使有时它们并不值得.:启用异常会增加二进制文件数据,延长编译时间(或许影响小),还可能加大地址空间的压力。·滥用异常会变相鼓励开发者去捕捉不合时宜,或本来就已经没法恢复的「伪异常」。比如,用户的输入不符合格式要求时,也用不着抛异常。如此之类的伪异常列都列不完。结论:从表面上看来,使用异常利大于弊,尤其是在新项目中,但是对于现有代码,引入异常会牵连到所有相关代码.如果新项目允许异常向外扩散,在跟以前未使用异常的代码整合时也将是个麻烦因为Google现有的大多数C++代码都没有异常处理,引人带有异常处理的新代码相当困难.鉴于Google现有代码不接受异常,在现有代码中使用异常比在新项目中使用的代价多少要大一些迁移过程比较慢,也容易出错,我们不相信异常的使用有效替代方案,如错误代码,断言等会造成严重负担。我们并不是基于哲学或道德层面反对使用异常,而是在实践的基础上:我们希望在Google使用我们自已的开源项目,但项目中使用异常会为此带来不便,因此我们也建议不要在Google的开源项目中使用异常.如果我们需要把这些项目推倒重来显然不太现实,对于Windows代码来说,有个特例(YuleFox注:对于异常处理,显然不是短短几句话能够说清楚的,以构造函数为例,很多C++书籍上都提到当构造失败时只有异常可以处理,Google禁止使用异常这一点,仅仅是为了自身的方便,说大了,无非是基于软件管理成本上,实际使用中还是自已决定2.7.86.8.运行时类型识别TODO34Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 • 很多现代语言都用异常。引入异常使得 C++ 与 Python, Java 以及其它类 C++ 的语言更一脉相 承。 • 有些第三方 C++ 库依赖异常,禁用异常就不好用了。 • 异常是处理构造函数失败的唯一途径。虽然可以用工厂函数(acgtyrant 注:factory function, 出自 C++ 的一种设计模式,即「简单工厂模式」)或 Init() 方法代替异常, 但是前者要求在堆栈分配 内存,后者会导致刚创建的实例处于”无效“状态。 • 在测试框架里很好用。 缺点: • 在现有函数中添加 throw 语句时,您必须检查所有调用点。要么让所有调用点统统具备最低限度 的异常安全保证,要么眼睁睁地看异常一路欢快地往上跑,最终中断掉整个程序。举例,f() 调用 g(), g() 又调用 h(), 且 h 抛出的异常被 f 捕获。当心 g, 否则会没妥善清理好。 • 还有更常见的,异常会彻底扰乱程序的执行流程并难以判断,函数也许会在您意料不到的地方返回。 您或许会加一大堆何时何处处理异常的规定来降低风险,然而开发者的记忆负担更重了。 • 异常安全需要 RAII 和不同的编码实践. 要轻松编写出正确的异常安全代码需要大量的支持机制. 更进一步地说, 为了避免读者理解整个调用表, 异常安全必须隔绝从持续状态写到“提交”状态的逻 辑. 这一点有利有弊 (因为你也许不得不为了隔离提交而混淆代码). 如果允许使用异常, 我们就不 得不时刻关注这样的弊端, 即使有时它们并不值得. • 启用异常会增加二进制文件数据,延长编译时间(或许影响小),还可能加大地址空间的压力。 • 滥用异常会变相鼓励开发者去捕捉不合时宜,或本来就已经没法恢复的「伪异常」。比如,用户的 输入不符合格式要求时,也用不着抛异常。如此之类的伪异常列都列不完。 结论: 从表面上看来,使用异常利大于弊, 尤其是在新项目中. 但是对于现有代码, 引入异常会牵连 到所有相关代码. 如果新项目允许异常向外扩散, 在跟以前未使用异常的代码整合时也将是个 麻烦. 因为 Google 现有的大多数 C++ 代码都没有异常处理, 引入带有异常处理的新代码相 当困难. 鉴于 Google 现有代码不接受异常, 在现有代码中使用异常比在新项目中使用的代价多少要大 一些. 迁移过程比较慢, 也容易出错. 我们不相信异常的使用有效替代方案, 如错误代码, 断言 等会造成严重负担. 我们并不是基于哲学或道德层面反对使用异常, 而是在实践的基础上. 我们希望在 Google 使 用我们自己的开源项目, 但项目中使用异常会为此带来不便, 因此我们也建议不要在 Google 的开源项目中使用异常. 如果我们需要把这些项目推倒重来显然不太现实. 对于 Windows 代码来说, 有个特例. (YuleFox 注: 对于异常处理, 显然不是短短几句话能够说清楚的, 以构造函数为例, 很多 C++ 书籍上都 提到当构造失败时只有异常可以处理, Google 禁止使用异常这一点, 仅仅是为了自身的方便, 说大了, 无 非是基于软件管理成本上, 实际使用中还是自己决定) 2.7.8 6.8. 运行时类型识别 TODO 34 Chapter 2. C++ 风格指南 - 内容目录
Google开源项目风格指南Tip:我们禁止使用RTTL定义:RTTI允许程序员在运行时识别C++类对象的类型.它通过使用typeid或者dynamic_cast完成优点:RTTI的标准替代(下面将描述)需要对有问题的类层级进行修改或重构.有时这样的修改并不是我们所想要的,甚至是不可取的,尤其是在一个已经广泛使用的或者成熟的代码中RTTI在某些单元测试中非常有用.比如进行工厂类测试时,用来验证一个新建对象是否为期望的动态类型.RTTI对于管理对象和派生对象的关系也很有用在考虑多个抽象对象时RTTI也很好用.例如:bool Base::Equal(Base*other)=O;bool Derived::Equal(Base* other){Derived* that=dynamic_cast<Derived*>(other);if (that=-NULL)return false;2缺点:在运行时判断类型通常意味着设计问题,如果你需要在运行期间确定一个对象的类型,这通常说明你需要考虑重新设计你的类随意地使用RTTI会使你的代码难以维护。它使得基于类型的判断树或者switch语句散布在代码各处.如果以后要进行修改,你就必须检查它们.结论:RTTI有合理的用途但是容易被滥用,因此在使用时请务必注意:在单元测试中可以使用RTTI,但是在其他代码中请尽量避免.尤其是在新代码中,使用RTTI前务必三思,如果你的代码需要根据不同的对象类型执行不同的行为的话,请考虑用以下的两种替代方案之一查询类型:虚函数可以根据子类类型的不同而执行不同代码。这是把工作交给了对象本身去处理如果这一工作需要在对象之外完成,可以考虑使用双重分发的方案,例如使用访问者设计模式.这就能够在对象之外进行类型判断.如果程序能够保证给定的基类实例实际上都是某个派生类的实例,那么就可以自由使用dynamic_cast.在这种情况下,使用dynamic_cast也是一种替代方案基于类型的判断树是一个很强的暗示,它说明你的代码已经偏离正轨了.不要像下面这样:352.7.6.其他C++特性
Google 开源项目风格指南 Tip: 我们禁止使用 RTTI. 定义: RTTI 允许程序员在运行时识别 C++ 类对象的类型. 它通过使用 typeid 或者 dynamic_cast 完成. 优点: RTTI 的标准替代 (下面将描述) 需要对有问题的类层级进行修改或重构. 有时这样的修改并 不是我们所想要的, 甚至是不可取的, 尤其是在一个已经广泛使用的或者成熟的代码中. RTTI 在某些单元测试中非常有用. 比如进行工厂类测试时, 用来验证一个新建对象是否为期 望的动态类型. RTTI 对于管理对象和派生对象的关系也很有用. 在考虑多个抽象对象时 RTTI 也很好用. 例如: bool Base::Equal(Base* other) = 0; bool Derived::Equal(Base* other) { Derived* that = dynamic_cast<Derived*>(other); if (that == NULL) return false; . } 缺点: 在运行时判断类型通常意味着设计问题. 如果你需要在运行期间确定一个对象的类型, 这通常 说明你需要考虑重新设计你的类. 随意地使用 RTTI 会使你的代码难以维护. 它使得基于类型的判断树或者 switch 语句散布在 代码各处. 如果以后要进行修改, 你就必须检查它们. 结论: RTTI 有合理的用途但是容易被滥用, 因此在使用时请务必注意. 在单元测试中可以使用 RTTI, 但是在其他代码中请尽量避免. 尤其是在新代码中, 使用 RTTI 前务必三思. 如果 你的代码需要根据不同的对象类型执行不同的行为的话, 请考虑用以下的两种替代方案之一 查询类型: 虚函数可以根据子类类型的不同而执行不同代码. 这是把工作交给了对象本身去处理. 如果这一工作需要在对象之外完成, 可以考虑使用双重分发的方案, 例如使用访问者设计模 式. 这就能够在对象之外进行类型判断. 如果程序能够保证给定的基类实例实际上都是某个派生类的实例, 那么就可以自由使用 dynamic_cast. 在这种情况下, 使用 dynamic_cast 也是一种替代方案. 基于类型的判断树是一个很强的暗示, 它说明你的代码已经偏离正轨了. 不要像下面这样: 2.7. 6. 其他 C++ 特性 35
Google开源项目风格指南if(typeid(*data)=typeid(D1)){else if(typeid(*data)==typeid(D2))(}elseif(typeid(*data)==typeid(D3))(旦在类层级中加入新的子类,像这样的代码往往会崩溃,而且,一旦某个子类的属性改变了,你很难找到并修改所有受影响的代码块不要去手工实现一个类似RTTI的方案.反对RTTI的理由同样适用于这些方案,比如带类型标签的类继承体系.而且,这些方案会掩盖你的真实意图2.7.96.9.类型转换Tip:使用C++的类型转换,如static_cast<>().不要使用inty=(int)x或inty=int(x)等转换方式;定义:C++采用了有别于C的类型转换机制,对转换操作进行归类优点:C语言的类型转换问题在于模棱两可的操作;有时是在做强制转换(如(int)3.5),有时是在做类型转换(如(int)"hello").另外,C++的类型转换在查找时更醒目.缺点:恶心的语法结论:不要使用C风格类型转换.而应该使用C++风格用static_cast替代C风格的值转换,或某个类指针需要明确的向上转换为父类指针时.用const_cast去掉const限定符。用reinterpret_cast指针类型和整型或其它指针之间进行不安全的相互转换.仅在你对所做一切了然于心时使用至于dynamic_cast参见6.8.运行时类型识别2.7.106.10.流Tip:只在记录日志时使用流36Chapter2.C++风格指南-内容目录
Google 开源项目风格指南 if (typeid(*data) == typeid(D1)) { . } else if (typeid(*data) == typeid(D2)) { . } else if (typeid(*data) == typeid(D3)) { . 一旦在类层级中加入新的子类, 像这样的代码往往会崩溃. 而且, 一旦某个子类的属性改变了, 你很难找到并修改所有受影响的代码块. 不要去手工实现一个类似 RTTI 的方案. 反对 RTTI 的理由同样适用于这些方案, 比如带类 型标签的类继承体系. 而且, 这些方案会掩盖你的真实意图. 2.7.9 6.9. 类型转换 Tip: 使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等 转换方式; 定义: C++ 采用了有别于 C 的类型转换机制, 对转换操作进行归类. 优点: C 语言的类型转换问题在于模棱两可的操作; 有时是在做强制转换 (如 (int)3.5), 有时是在 做类型转换 (如 (int)"hello"). 另外, C++ 的类型转换在查找时更醒目. 缺点: 恶心的语法. 结论: 不要使用 C 风格类型转换. 而应该使用 C++ 风格. • 用 static_cast 替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针 时. • 用 const_cast 去掉 const 限定符. • 用 reinterpret_cast 指针类型和整型或其它指针之间进行不安全的相互转换. 仅在你 对所做一切了然于心时使用. 至于 dynamic_cast 参见6.8. 运行时类型识别. 2.7.10 6.10. 流 Tip: 只在记录日志时使用流. 36 Chapter 2. C++ 风格指南 - 内容目录
Google开源项目风格指南定义:流用来替代printf()和scanf()优点:有了流,在打印时不需要关心对象的类型不用担心格式化字符串与参数列表不匹配(虽然在gcc中使用printf也不存在这个问题).流的构造和析构函数会自动打开和关闭对应的文件缺点:流使得pread()等功能函数很难执行.如果不使用printf风格的格式化字符串,某些格式化操作(尤其是常用的格式学符串%.*S)用流处理性能是很低的.流不支持学符串操作符重新排序(%1s),而这一点对于软件国际化很有用结论:不要使用流,除非是日志接口需要:使用printf之类的代替使用流还有很多利弊,但代码一致性胜过一切,不要在代码中使用流拓展讨论:对这一条规则存在一些争论,这儿给出点深层次原因.回想一下唯一性原则(OnlyOneWay):我们希望在任何时候都只使用一种确定的I/O类型,使代码在所有I/O处都保持一致.因此我们不希望用户来决定是使用流还是printf+read/write.相反,我们应该决定到底用哪一种方式.把日志作为特例是因为日志是一个非常独特的应用,还有一些是历史原因流的支持者们主张流是不二之选,但观点并不是那么清晰有力,他们指出的流的每个优势也都是其劣势。流最大的优势是在输出时不需要关心打印对象的类型。这是一个亮点,同时,也是一个不足:你很容易用错类型,而编译器不会报警,使用流时容易造成的这类错误cout << this;//输出地址cout<<*this;//输出值由于<<被重载,编译器不会报错,就因为这一点我们反对使用操作符重载有人说printf的格式化丑陋不堪,易读性差,但流也好不到哪儿去.看看下面两段代码吧,实现相同的功能,哪个更清晰?cerr <<"Error connecting to "<< foo->bar()->hostname.first<<":"<< foo->bar()->hostname.second <<";"<<ustrerror(errno);fprintf(stderr,"Errorconnectingto%s:%u:%s"foo->bar()->hostname.first,foo->bar()->hostname.second,strerror(errno));你可能会说,“把流封装一下就会比较好了”,这儿可以,其他地方呢?而且不要忘了,我们的目标是使语言更紧凑,而不是添加一些别人需要学习的新装备每一种方式都是各有利弊,“没有最好,只有更适合”:简单性原则告诚我们必须从中选择其一,最后大多数决定采用printf+read/write372.7.6.其他C++特性
Google 开源项目风格指南 定义: 流用来替代 printf() 和 scanf(). 优点: 有了流, 在打印时不需要关心对象的类型. 不用担心格式化字符串与参数列表不匹配 (虽然在 gcc 中使用 printf 也不存在这个问题). 流的构造和析构函数会自动打开和关闭对应的文件. 缺点: 流使得 pread() 等功能函数很难执行. 如果不使用 printf 风格的格式化字符串, 某些格式化 操作 (尤其是常用的格式字符串 %.*s) 用流处理性能是很低的. 流不支持字符串操作符重新 排序 (%1s), 而这一点对于软件国际化很有用. 结论: 不要使用流, 除非是日志接口需要. 使用 printf 之类的代替. 使用流还有很多利弊, 但代码一致性胜过一切. 不要在代码中使用流. 拓展讨论: 对这一条规则存在一些争论, 这儿给出点深层次原因. 回想一下唯一性原则 (Only One Way): 我们希望在任何时候都只使用一种确定的 I/O 类型, 使代码在所有 I/O 处都保持一致. 因此, 我们不希望用户来决定是使用流还是 printf + read/write. 相反, 我们应该决定到底用哪 一种方式. 把日志作为特例是因为日志是一个非常独特的应用, 还有一些是历史原因. 流的支持者们主张流是不二之选, 但观点并不是那么清晰有力. 他们指出的流的每个优势也都 是其劣势. 流最大的优势是在输出时不需要关心打印对象的类型. 这是一个亮点. 同时, 也是 一个不足: 你很容易用错类型, 而编译器不会报警. 使用流时容易造成的这类错误: cout << this; // 输出地址 cout << *this; // 输出值 由于 << 被重载, 编译器不会报错. 就因为这一点我们反对使用操作符重载. 有人说 printf 的格式化丑陋不堪, 易读性差, 但流也好不到哪儿去. 看看下面两段代码吧, 实 现相同的功能, 哪个更清晰? cerr << "Error connecting to '" << foo->bar()->hostname.first << ":" << foo->bar()->hostname.second << ": " <<␣ ,→strerror(errno); fprintf(stderr, "Error connecting to '%s:%u: %s", foo->bar()->hostname.first, foo->bar()->hostname.second, strerror(errno)); 你可能会说, “把流封装一下就会比较好了”, 这儿可以, 其他地方呢? 而且不要忘了, 我们的 目标是使语言更紧凑, 而不是添加一些别人需要学习的新装备. 每一种方式都是各有利弊, “没有最好, 只有更适合”. 简单性原则告诫我们必须从中选择其 一, 最后大多数决定采用 printf + read/write. 2.7. 6. 其他 C++ 特性 37