谷歌C++风格拾遗



谷歌C++风格文档真是一个好东西,为C++开发提供了一个便捷高效又有无数人在实践和验证的白皮书,虽然其中并不是所有说法都是客观的,但是既然是经过谷歌这样的公司投入实际应用的,那总不会有很大的坏处,至少不会给你带来麻烦,所以我个人一直都比较坚持使用这套风格。

但是注意对于Windows程序员,对于谷歌这套风格来说,确实是有点不适合,比如异常,比如多重继承,比如缩进、换行符等,因为Windows自成一体系以及经历了历史的沉淀,当你接手一个项目时发现缩进都是Tab换行符都是Win样式,那你也不可能就改成2个空格,这样会让code base变得无比混乱,所以在实际项目中使用谷歌C++风格的时候,视目前情况来确定哪些风格可用,哪些应该不用就是了。

- 头文件原则 -
1、如果要使用一个声明在某个头文件中的函数,应该总是include那个头文件,也就是如果你实现一个函数,应该将其实现在一个.c文件中,然后把函数声明暴露在.h文件,而外部使用者应该include这个.h文件。
2、使用类模板的时候,应该include这个类的头文件。
3、如果使用的是一个普通类,那向前声明而不include类头文件是可以的,但是你要确定向前声明来使用不会出现其他问题,如果你不确定,那遵循一律include头文件原则。

头文件的包含顺序:
1、C语言库
2、C++的库
3、项目中的第三方库的头文件
4、当前项目的头文件

而include组织原则应该按照当前项目中的目录树结构来组织,比如项目代码“src/core/log.h”,在include时则应该是“core/log.h”。

- 命名空间 -
可以在源码文件(CPP\CC)中使用【未命名空间】来避免运行时的名称冲突,注意命名空间格式不缩进。

- 类的一些注意 -
原则:应避免在类的构造函数中进行复杂的初始化,尤其是是调用可能初始化失败的函数或者是调用虚函数。
构造函数失败的情况除了抛出异常没有其他办法知道是否初始化失败,而谷歌推荐不使用C++异常处理;
而如果构造函数初始化失败,我们得到的这个新的类的内部状态是“不清不楚”的,我们不知道是否该继续调用此类的其他方法,还是干脆销毁类;
如果在构造函数中调用虚函数,那就会依赖于子类的虚函数实现,出现不可控制的情况,但是如果有这种奇葩的需求的话可以例外。

原则:写一个默认的构造初始化函数。
如果类有成员变量,你应该写一个默认的构造函数用于将这些变量进行初始化,否则编译器会生成类似malloc这样的挫B构造函数。
在创建新的对象的时候将其默认初始化为一个“无效”的状态,以方便错误判断,但是对写这个类的人来说,这个工作显得有多余。
而如果你的类定义了成员变量但是没有初始化构造函数,则编译器提供的默认初始化函数可能把你这些变量初始化为一个未知的值\状态。

原则:使用显示构造函数。
为了避免隐式的类构造,应使用explicit关键字,具体大家可以百度此关键字。

原则:尽量不要使用复制构造和赋值运算符来复制对象。
赋值构造函数在编译器处理时可以带来一点好处,现代编译器会有一些特定的优化,当然主要是很多时候写起来很爽。
但是只有小部分类是可复制创建新的对象的,大部分类都不需要这样的功能,很多情况下指针和引用可以部分的替代这样的复制构造而且可以带来更高效率的性能。
如果你的类是可以复制创建的,则最后应该提供一个CopyFrom或者CopyTo方法,而不是使用复制构造函数,因为这些方法不能被隐式调用,在调试的时候也能更快的定位处理点,但是为了某些情况,比如STL兼容的问题,你可以提供复制构造函数来包装这些方法。
这里还有一个建议,如果你的类不需要复制构造函数,那你应该显示禁止它,这样就可以在某些不可预料的隐式使用的时候让编译时出现错误。

原则:尽量使用class,而不是struct。
在C++中,class和struct的区别仅仅是默认访问权限上的区别,他们几乎就是一样的。
但是struct应该尽量使用在数据对象的存改上,如果struct的功能提示到了“方法”级别,则应该使用class来替换struct,struct应该保持它作为数据体存储空间的纯洁性。
比如当有构造函数、析构函数、初始化方法的时候,则不应该使用struct。
如果不知道该用class还是struct,那就用class吧。
(不过,为了和STL保持一致性,应该使用struct而不是class来实现仿函数特性)

- 继承 -
原则:不要过渡使用继承,用组合会更好。
原则:如果有必要的话,将你类的析构函数写成虚的,如果你的类中有虚方法的话,那这个类的析构函数必需应该是虚的。
原则:对于可能被子类使用的函数声明为protected方法,要限制对它们的使用,而要注意每个类的数据成员应该是private的。
原则:在子类重定义一个虚函数来实现继承时,应该显示写virtual关键字标识这个是虚继承来的函数,原因是,如果virtual关键字没了,则阅读者就要去确定这个方法是不是虚继承来的。

- 多重继承 -
原则:应该尽量避免多重继承,而换为使用组合接口实现。
原则:我们只允许有实现的基类不超过一个的多重继承(2重继承),所以其他的应该都使用接口来实现。
我们将基类分为有实现和无实现(纯接口)二种。
如果一个类满足下面的情况,它就是一个纯接口类:
1、它只有公开的纯虚方法(= 0),和静态方法;
2、它没有非静态的数据成员;
3、它不需要定义任何构造函数;
一个接口类不能生成一个新的实例对象,为了保证这个接口所有的实现都可以被直接销毁,这个接口必需有一个虚的析构函数。
原则:用interface这样的关键字来表明一个类是一个接口类,同时禁止向其中添加方法实现或者封静态的数据成员。

- 重载操作符 -
原则:除非有特殊情况,不然不要重载操作符。
1、重载操作符会误导阅读者的感觉特别是新手,他会认为这个操作十分廉价;
2、重装操作符在寻找位置的时候不好找,因为代码都是==、++;
3、重载操作符在某些情况下可能冲突或者导致bug;
一般来说不要重载操作符,而应该使用像Equals()这样的方法来进行类似操作符的比对,如果这个类有向前声明,就不要重载危险的一元操作符&。
但是也有一些特殊情况,比如需要跟STL交互,则需要实现==这样的操作符或者仿函数的时候,那可以允许。

- 类的访问控制 -
原则:类的数据成员应该为私有的,要暴露应该提供Set\Get方法,类似C#中的属性系统,对C++来说,Set\Get方法应该尽量保证inline。

- 类的声明顺序 -
原则:在类中的声明顺序应该为,public高于private,methods高于members。
你定义的类应该从public成员开始,接着是protected,然后是private。
在每个部分中,应该按照下面的顺序,比如:
private
 - 自定义的typedef或者enums。
 - 常量(static const)。
 - 构造函数
 - 析构函数
 - 方法,以及静态方法
 - 数据成员(除了静态常量成员)
友元声明应该总是在private部分。

- 所有按引用传递的参数都应该是const的 -
原则:我们坚持引用传递使用const,而如果要修改,则应该使用指针。
例如:void foo(const String& in, String* out)

- 函数重载 -
原则:只有当阅读者一看就知道这个函数执行了什么,而不需要去看哪个版本的函数被调用了,不然不要使用函数重载。
重载能令代码更直接美观,对于模板化的代码重装也是必需的。但是如果一个函数只通过参数来区别各个重载的版本,则需要去理解C++复杂的匹配优先原则才能正确的使用和阅读。而如果一个派生类只重写了一部分函数,许多人也会被继承的语义所混乱视觉。
如果你想重载一个函数,考虑一下将函数的名字写成带有一定的参数信息,比如AppendString和AddInt,而不是用一个Append或者Add去处理各个类型。

- 函数的默认参数 -
原则:尽量不使用函数的默认参数,这可能会导致和重载函数混乱。

- 不允许使用变长数组和alloca函数 -
原则:变长数组并不是C++标准的一部分,部分编译比如MSVC并不对其进行支持。

- 友元 -
原则:如果可能,尽可能的使用public方法来进行访问控制的沟通。

- 异常 -
(这里争议比较大,C++异常我实在不好评价什么,看你自己了。)
原则:我们不使用异常。

- RTTI -
原则:尽量不使用RTTI,除非在某些特殊的情况比如单元测试的时候需要便捷也不考虑性能效率的测试父类型或者判断类型。
因为RTTI会时代码难以维护,它会使代码遍地都是if和case,而且如果要频繁的检查对象类型就证明目前的架构设计其实是有问题的。

- 类型转换 -
原则:使用C++风格的类型转换,而不是使用C语言风格的。
这样可以使在批量搜索代码的时候快速定位类型转换关键字,也更加鲜明,主要是符合C++的特性。
1、使用static_cast进行数值的转换,比如int64转换int32,或是显示将一个类的指针转换为它的子类的指针;
2、使用const_cast去掉const特性;
3、使用reinterpret_cast做类似C语言的不安全强制转换;

- IO和Stream -
原则:如果成员有IO和Stream操作,则它应该提供一套IO的ReadWrite接口。

- ++i和i++ -
原则:忽略返回值的时候,前缀形式(++i)至少不会比后缀形式效率低,甚至更高,因为后缀形式自加或者自减,需要保存i的原始值作为表达式的值,如果i是迭代器或者其他被标准类型,复制操作的代价可能更大。
所以我们应该尽量使用前缀++形式,特别是使用STL迭代器的情况。

- 数和类型 -
原则:0用于整数,0.0(f)用于浮点数,nullptr用于指针,'\0'用于字符串。

- 命名规范 -
1、文件名应该都是小写,使用下划线来分割词义;
2、类型名称以大写字母开头,即大驼峰,类型名称泛指类名,方法名等;
3、变量名都应该是小写,以下划线分割;
4、数据成员名称应该使用小驼峰;
5、全局变量使用g打头,静态常量使用k;
6、使用声明为废弃的flag来标识旧的方法;

- 制表符\换行符 -
我们推荐使用2个空格代替Tab,使用Linux风格的换行符而不是Windows的。

- 函数声明 -
1、返回值类型要和声明在同一行;
2、开括号“(”要和声明在同一行;
3、在函数名和开括号之间不要有空格;
4、在括号和参数之间不要有空格;
5、在开和括号和闭的括号之间应该有一个空格;
6、所有参数都应该有名字,实现和声明中的相同;
7、如果可能,所有参数都对齐;
8、换行后的参数有4个空格的缩进(默认2个空格);

- 参数调用 -
1、如果参数放不进一行,就把他们写为多行;
2、如果是多行,尽量一行一个参数;
3、如果是多行,尽量第一个参数跟调用开括号在一起;

- 其他 -
1、应该编写短小精干的小函数,然后组合成大函数,尽量避免所有代码都在一个大函数体内。
2、尽量使用智能指针,比如C++11的shared_ptr,boost里面的也有,特别是在共享一个对象所有权的时候。
3、能用常量尽量用常量,使用常量代替不变数的宏值。
4、尽可能避免使用宏,真要使用,不要声明在头文件中,在用到的define,使用后立即undef,不要使用那些展开后不稳定不兼容的宏。
5、C++内建类型中,只使用int,而要使用其他类型,应该使用stdint内的带有精度标志的类型。
6、不要使用unsigned类型,而使用int,而且应该把int假设为最大32位。记住,32位的数值最大应该是从负到正,除非你要进行符号扩展的时候,而如果数大宇int的容量,应该使用int64。
7、写代码的时候时刻考虑64位移植的问题。
8、使用sizeof(变量名)来计算大小而不是使用sizeof(类型名),因为这样可以在变量改变类型的时候,sizeof也是正确的不需要修改。
9、只使用C++11和boost库中被广泛使用且被认可安全的部分。
10、预处理指令不应该缩进,应该一直都是在行顶。
11、命名空间内不需要缩进。
12、Windows下如果你确定不使用异常,应该对某些库比如WTL\ATL使用类似_ATL_NO_EXECPTIONS这样的来禁止异常。
13、Windows下应该永远只有一个预编译源文件包含预编译头文件,资源文件通常被命名为resource.h并且只包括宏。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值