《Google的C++编码规范》阅读随笔 —— 第四章 智能指针和其他C++特性

1.智能指针
如果确实需要使用智能只恨的话,scoped_ptr完全可以胜任。早费城特殊的情况下,例如对STL容器中对象,你应该只使用std::tr1::shared_ptr,任何情况下都不要使用auto_ptr。

只能指针看上去是指针,其实是附加了语义的对象,以scoped_ptr为例,scoped_ptr被销毁时,删除了他所指向的对象。shared_ptr也是如此,而且,shared_ptr使用了引用计数。从而只有当它所指向的最后一个对象被销毁时,指针才会被删除。

一般来说,我们倾向于设计对象隶属明确的代码,最明确的对象隶属是根本不使用指针,直接将独享作为一个域或局部变量使用。另一种极端是引用计数指针不属于任何对象,这样设计的问题容易导致循环引用或其他导致对象无法删除的诡异条件,而且在每一次拷贝或赋值时连原子操作都会很慢。

虽然不推荐这么做,但有些时候,引用计数指针是最简单有效的解决方案。

其他C++特性
1.引用参数
所有按引用传递的参数必须加上const

定义:在C语言中,如果函数需要修改变量的值,形参必须为指针,如int foo(int *pval).
在C++中,函数还可以声明引用参数:int foo(int &val).

优点:定义形参为引用避免了想(*pval)++这样丑陋的代码,像拷贝构造函数这样的应用也是必须的,而且不像指针那样不接受空指针NULL;

缺点:容易引起误解,因为引用在语法上是值却拥有指针的语义。

结论:函数形参表中,所有引用必须是const:
void Foo(const string &in, string *out);

事实上这是一个硬性约定,输入参数为值或者常数引用,输出参数为指针;输入参数可以是常数指针,但不可能使用非常数引用形参

在强调参数不是拷贝而来,在对象生命周期内必须一直存在随时可以使用的常数指针,最好将这些在注释中详细说明。bind2nd和mem_fun等STL适配器不接受引用形参,这种情况下也必须以指针形参声明函数

2.函数重载
尽在输入参数类型不同、功能相同时使用用重载函数(含构造函数),不要使用函数重载模仿缺省函数参数

定义:可以定义一个函数参数类型为const string&,并定义其重载函数类型为 const char*。
class MyClass{
public:
void Analyze(const string &text);
void Analyze(const char *text, size_t textlen);
};

优点:通过重载不同参数的同名函数,令代码更加直观,模板化代码需要重载,同时为访问者带来便利。

缺点:限制使用重载的一个原因是在特定的调用处很难确定到底调用的是哪一个函数,另一个原因是当派生类只重载函数的部分变量会令很多人对继承语义产生困惑。此外在阅读库的客户端代码是,因缺省函数参数造成不必要的费解。

结论:如果你想重载一个函数,考虑让函数名包含参数信息,例如,使用AppendString(),AppendInt(),而不是Append()。

3.缺省参数
禁止使用缺省函数参数

优点:经常用到一个函数带有大量缺省值,偶尔重写一下这些值,缺省参数为很少涉及的例外情况提供了少定义一些函数的方便。

缺点:大家经常会通过查看现有代码确定如何使用API,缺省参数会使得复制粘贴以前的代码难以呈现所有参数,当缺省参数不适用于新代码时可能导致重大问题

结论:所有参数必须明确指定,强制程序员考虑API和传入的各参数值,避免使用可能不为程序员所知的缺省参数。

4.变长数组和alloca
禁止使用变长数组和alloca()。

优点:变长数组具有浑然天成的语法,变长数组和alloca()也都很高效

缺点:变长数组和alloca()不是标准C++的组成部分,更重要的是,它们在堆栈(stack)上根据数据分配大小可能导致难以发现的内存泄漏:在我的机器上运行的好好地,到了产品中却莫名其妙的挂掉了

结论:使用安全的分配器(allocator),如scoped_ptr/scoped_array。

5.友元
允许合理使用友元类及友元函数。

通常将友元定义在同一文件下,避免读者跑到其他文件中查找其对某个类私有成员的使用。经常用到友元的一个地方是将FooBuilder声明为Foo的友元,FooBuilder易边可以正确构造Foo的内部状态,而无需将该状态暴露出来。某些情况下,讲一个单元测试用类声明为待测类的友元会很方便

友元延伸了(但没有打破)类的封装界限,当你希望只允许一个类访问某个成员时,使用友元通常比将其声明为public要好得多。当然,大多数类应该只提供公共成员与其交互。

6.异常
不要使用C++异常

优点:
1)异常允许上层应用决定如何处理在底层嵌套函数中发生的“不可能发生“的失败,不想出错代码的记录那么模糊费解。
2)应用于其他很多现代语言中,引入异常使得C++与Python、java以及其他与C++相近的语言更加兼容
3)组多C++第三方库使用异常,关闭异常将导致难以与之结合
4)异常是解决构造函数失败的唯一方案,虽然可以通过工厂函数或Init()方法模拟异常,但他们分别需要堆分配或新的“非法”状态
5)则测试框架找那个,异常确实很好用

缺点:
1)在现有函数中添加throw语句时,必须检查所有调用处,即使他们致至少具有基本的异常安全维护,或者程序正常结束,永远不可能捕获该异常。例如:如果f()依次调用了g()和h(),h跑出被f捕获的异常,g就要当心了,避免没有完全清理
2)通俗一点说,异常会导致程序控制流通过查看代码无法确定:函数有可能在不确定的地方返回,从而导致代码管理和调试困难,当然,你可以通过规定何时何地如何使用异常来最小化的降低开销,却给开发人员带来掌握这些规定的负担
3)异常安全需要RAII和不同编码实践。轻松、正确编写异常安全代码需要大量支撑,允许使用异常
4)加入异常使二进制执行代码体积变大,增加了编译时长,还可能增加地址空间压力
5)异常的实用性可能会刺激开发人员在不恰当的时候抛出异常,或者在不安全的地方从异常中恢复,例如:非法用户输入可能导致抛出异常。如果允许使用异常会使得这样一篇编程风格指南长出很多。

结论:表面上看,使用利大于弊,尤其在新项目中,然而,对于现有代码,引入异常会牵连到所有依赖代码。如果允许异常在新项目中使用,在跟以前没有使用的代码整合时也是一个麻烦,因为google现有的大多数C++代码都没有异常处理

实际使用中还是自己决定是否使用异常

7.运行时识别(Run-time Type Information, RTTI)
我们禁止使用RTTI

定义:RTTI允许程序员在运行时识别C++对象的类型

优点:RTTI在某些单元测试中非常有用,如在进行工厂类测试时用于检验一个新建对象是否为期望的动态类型。除了测试外,极少用到

缺点:运行时识别类型意味着设计本身有问题,如果你需要在运行期间确定一个对象的类型,这通常说明你需要重新考虑你的类的设计

结论:出单元测试外,不要使用RTTI,如果你发现需要所写代码因对象类型不同而动作各异的话,考虑换一种方式识别对象类型。

虚函数可以实现随子类类型不同而执行不同代码,工作都是交给对象本身去完成

如果工作在对象之外的代码中完成,考虑双重分发方案,如Visitor模式,可以方便的在对象本身之外确定类的类型

如果你认为上面的方法你掌握不了,可以使用RTTI,但务必请三思,不要去手工实现一个貌似RTTI的方案。我们反对使用RTTI,同样反对贴上类型标签的貌似类继承的替代方案(译者注:使用就使用吧,不使用也不要造轮子)

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

定义:C++引入了有别于C的不同类型的类型转换操作

优点:C语言的类型转化问题在于操作比较含糊:有时是在做强制转换((int)3.5),有时是在做类型转换((int)"hello").另外,C++的类型转换查找更加容易,更醒目

缺点:语法比较恶心

结论:使用C++风格而不要使用C风格类型转换
1)static_cast:和C风格转换相似可做值的强制转换,或指针的父类到子类的明确向上转换
2)const_cast:移除const属性
3)reinterpret_cast:指针类型和整型或其他指针间不安全的相互转换,仅在你对所做的一切了然于心时使用
4)dynamic_cast:除测试外不要使用,除单元测试外,如果你需要在运行时确定类型信息,说明设计有缺陷

9.流
只在记录日志时使用流。

定义:流是printf和scanf的替代

优点:有了流,在输出时不需要关心对象的类型,不用担心格式化字符串与参数列表不匹配(虽然在gcc中使用printf也不存在这个问题),打开。关闭对应文件时,流可以自动构造、析构。

缺点:流使得pread()等功能函数很难执行,如果不使用printf之类的函数而是使用流很那对格式进行操作(尤其是常用的格式字符串%.*s),流不支持字符串操作符重新定序(%1s),而这一点对国际化很有用。

结论:不要使用流,除非是日志接口需要,使用printf之类的代替
使用流还有很多利弊,代码一致性胜过一切,不要再代码中使用流

拓展:
我们希望在任何时候只使用一种确定IO类型,使代码在所有IO处保持一致。
目前多数还是决定printf+read/write

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

优点:不考虑返回值的话,前置自增通常比后置自增效率更高,因为后置自增需要对表达式的值i进行一次拷贝。如果i是迭代器或者非数值类型,拷贝的代价比较大。

11.const的使用
我们强烈建议在任何可以使用的情况下都要使用const

定义:在声明的变量或参数前加上关键字const用于指明变量值不可修改,为类中的函数加上const(const放在形参的括号之后)限定表明该函数不会修改类成员变量的状态。

优点:人们更容易理解变量是如何使用的,编辑器可以更好地进行类型检查、更好的生成代码。即使在无锁的多线程编程中,人们也知道什么样的函数是安全的

缺点:如果你向一个函数传入const变量,函数原型中也必须是const的(否则需要const_cast类型转换),在调用库函数时这尤其是个麻烦

结论:const变量、数据成员、函数和参数为编译时类型检查增加了一层保障,更好的尽早发现错误。
1)如果函数不会修改传入的引用或指针类型的参数,这样的参数应该为const
2)尽可能将函数声明为const,访问函数应该总是const,其他函数如果不会修改任何数据成员也应该是const,不要返回对数据成员的非const引用或指针
3)如果数据成员在对象构造之后不再改变,可将其定义为const

然而,也不要对const过度使用,想const int* const* const x;就有些过了,其实写为const int** x;就可以了。

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

const位置:
有人喜欢int const foo形势不喜欢const int foo;
这里我们提倡const在前,因为const修饰的是int。更符合自然语言

12.整型
C++内建整型中,唯一用到的是int,如果程序中需要不同大小的变量,可以使用中的精确宽度的整型,如int16_t;

定义:C++没有指定整型的大小,通常人们人为short是16位,int是32位,龙是32wei,long long是64位。

优点:保持声明统一。

缺点:C++中整型大小因编译器和体系结构的不同而不同。

结论:定义了int16_t,uint32_t,int64_t等整型,在需要确定大小的整形师可以使用它们代替short,unsigned long long 等,在C整型中,只使用int。适当情况下,推荐使用标准类型如size_t和ptrdiff_t。

最常使用的证书通常不会太大,需要64位整型的话,可以使用int64_t or uint64_t。

无符号整型:
推荐使用无符号整型表示非负整数。类型表明了数值取值形式。但是,在C语言中,这一优点被bugs所淹没,如:
for(unsigned int i = foo.Length() - 1; i >= 0; --i)
这个循环永不终止。

因此,使用断言声明变量为非负数,不要使用无符号整型。

13.64位下的可移植性
代码在64位和32位的系统中,原则上应该都比较友好,尤其对输出、比较、结构对齐来说:
1)printf()指定的一些类型在32位和64位系统上可移植性不是很好,C99标准定义了一些可移植的格式。不幸的是,MSVC7.1并非全部支持。而且标准中也有所遗漏
2)记住sizeof(void*) != sizeof(int),如果需要一个指针大小的证书要使用intptr_t。
3)需要对结构对齐加以留心,尤其是对于存储在磁盘上的结构体。在64位系统中,任何拥有int64_t/uint64_t成员的类或结构体将默认被处理为8字节对齐。如果32位和64位代码共用磁盘上的结构体,需要确保两种体系结构下的结构体的对齐一致。大多数编译器提供了调整结构体对齐的方案。gcc中可使用attribute((packed)),MSVC提供了#pragma pack()和_declspec(align())(注,解决方案的项目属性里也可以直接设置)
4)创建64位常量时使用LL或ULL作为后缀,如:
int64_t my_value = 0x123456789ll;
uint64_t my_mask = 3ULL << 48;
5)如果你确实需要32位和64位系统具有不同代码,可以在代码变量前使用(尽量不要这么做,使用时尽量使修改局部化)

14.预处理宏
使用宏时要谨慎,尽量以内联函数、枚举和常量代替之。
下面给出的用法模式可以避免一些使用宏的问题,供使用宏时参考:
1)不要在h文件中定义宏
2)使用前正确#define,使用后正确#undef
3)不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称;
4)不使用会导致不稳定的C++构造的宏,至少文档说明其行为

15.0和NULL
整数用0,实数0.0,指针NULL字符串'\0'

16.sizeof
尽可能用sizeof(varname)代替sizeof(type)
因为使用sizeof(varname)可以再代码改变是自动同步变量类型

17.Boost库
只使用Boost中被认可的库。
目前比较成熟的Boost库的子集有:
1)Compressed Pair : boost/compressed_pair.hpp

  1. Pointer Container: boost/ptr_container 不包括ptr_array.hpp和序列化(serialization)
    当然,不必拘泥于该规则

总结:
1)对于智能指针,安全第一、方便第二,尽可能局部化(scoped_ptr)
2)引用形参加上const,否则使用指针形参
3)函数重载要清晰,易读
4)鉴于容易误用,禁止使用缺省函数参数(值得商榷)
5)禁止使用变长数组
6)合理使用友元
7)为了方便代码管理,禁止使用异常(值得商榷)
8)禁止使用RTTI,否则重新设计代码吧
9)使用C++风格的类型转化,除单元测试外,不要使用dynamic_cast
10)使用流还是printf+read/write, it is a problem
11)能用前置自增,自减就不用后置自增,自减
12)const能用则用,提倡const在前
13)使用确定大小的整型,除位组外不要使用无符号型
14)格式化输出及结构对齐时,注意32位和64位的系统差异
15)除字符串化,链接外尽量避免使用宏
16)字符串'\0',整数0,实数0.0,指针NULL
17)用sizeof(varname)代替sizeof(type)
18)只使用Boost中被认可的库

你可能感兴趣的:(《Google的C++编码规范》阅读随笔 —— 第四章 智能指针和其他C++特性)