不要在构造函数中进行复杂的初始化操作,特别是那些可能失败的操作和调用虚函数的操作。
如果不使用异常(不推荐使用异常)构造函数无法表述一个错误。如果在构造函数中执行初始化失败,那么我们将得到一个处于未知状态的对象。
如果在构造函数中调用了虚函数,那么对虚函数的调用不会传递到子类,这可能会引人迷惑。
如果有人定义了一个这个类类型的全局变量(这是违反规范的,但有人仍然会这样做),那么构造函数将在main()函数之前被调用,这时构造函数中某些隐式的依赖可能并不成立。
2.默认构造函数
当你new一个对象并且不传入任何参数时,默认构造函数将被调用。new[]总是调用默认构造函数。
如果一个类定义了成员变量,则应该总是定义默认构造函数。因为编译器生成的默认构造函数也许并不能正确(或是以你所期望的方式)初始化对象。
3.Explicit构造函数
只有一个参数的构造函数,应该使用explicit关键字声明。如果不使用explicit关键字,那么这个构造函数将可以被用作隐式类型转换,这可能并不是我们所希望的。如果构造函数确实要用于隐式类型转换,则需要在注释中明确说明。
拷贝构造函数不需要使用explicit关键字声明。
4.拷贝构造函数
只在必要的时候才定义拷贝构造函数和赋值操作符。在其他情况下,使用宏DISALLOW_COPY_AND_ASSIGN来禁止编译器生成它们。
拷贝构造函数通常拥有比CopyFrom()之类的拷贝函数更好的性能,因为它将对象的构建和拷贝结合在一起。
只有极少的情况下对象才需要被拷贝,比如STL容器要求对象是可拷贝和赋值的,但是大多数情况下在STL中存储对象的指针同样可以达到目的并且可以提供更好的性能。
如果你的类需要拷贝,提供一个拷贝方法如CopyFrom()或Clone(),因为这些方法需要被显式调用从而避免由拷贝构造函数带来的隐藏的可能Bug。如果定义一个拷贝方法不能满足需求(比如出于性能原因或者需要存放在STL容器中),那么定义拷贝构造函数和赋值操作符。
如果不需要拷贝构造函数和赋值操作符,那么在private区段声明它们并且不提供定义,这样以任何方式使用它们都将导致链接错误。
DISALLOW_COPY_AND_ASSIGN宏定义如下:
// A macro to disallow the copy constructor and operator= functions
// This should be used in the private: declarations for a class
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&); \
TypeName& operator=(const TypeName&)
然后如下使用:
class Foo {
public:
Foo(int f);
~Foo();
private:
DISALLOW_COPY_AND_ASSIGN(Foo);
};
5.结构体和类
结构体和类在C++中几乎完全一样。不同之处在于结构体的成员默认是公有的,而类的成员默认是私有的。
结构体应该仅用于承载数据,并且只具有构造函数、析构函数及其它一些简单操作数据成员的方法。
结构体的成员变量命名方法与类的成员变量命名方式不同。
6.继承
继承分为实现继承和接口继承(类似Java的继承和接口实现),实现继承将实实在在地继承基类中的代码,而接口继承只继承基类中的方法声明。
通常情况下组合比继承更好。如果要使用继承,应该使用public继承。如果你认为有必要使用private继承,请优先考虑包含基类的一个实例。
将析构函数声明为virtual。如果你的类有虚函数,那么它的析构函数应该声明为virtual。
如果要子类中重写基类的virtual方法,那么在方法声明中明确使用virtual声明它是一个虚方法。
7.多继承
将多继承的使用限定在只有一个基类具有实现,其它基类都是pure interfaces的情况(与Java的继承和接口实现机制相似)。
8.接口
一个类如果满足以下条件则称为pure interface:
只有public的virtual (" = 0 ")方法和静态方法。
没有非静态数据成员。
不需要任何构造函数。
一个接口类的析构函数应该声明为virtual。
9.操作符重载
通常情况下,不要重载操作符。
为了让某些模板正确工作,你可能需要定义相应的操作符。
为了和STL协同工作,你可能需要定义operator==和operator<,但是请优先考虑创建equality and comparison functor types。
10.访问控制
数据成员应声明为private,如有需要可以提供相应的accessor和mutator方法。
通常一个数据成员应命名为foo_,accessor命名为foo(),mutator命名为set_foo()。
static const数据成员(通常命名为kFoo)不需要声明为private。
accessor和mutator通常在头文件中定义为inline函数。
11.声明顺序
类应按如下顺序进行声明:public区段、protected区段、private区段。
在每一个区段内部,应按如下顺序进行声明:
Typedefs and Enums
Constants (static const data members)
Constructors
Destructor
Methods, including static methods
Data Members (except static const data members)
Friend declarations应该在private区段声明,并且DISALLOW_COPY_AND_ASSIGN 宏应该在private区段的最后。
cc文件中方法的定义顺序应与头文件中方法声明的顺序一致。
12.编写简短的函数
对于函数的长度并没有硬限制,不过通常情况下,当函数超过40行应该考虑是否应该在不破坏程序结构的情况下将它进行拆分。
V Google-Specific Magic
1.智能指针
智能指针在防止内存泄漏和编写exception-safe的代码方面非常有用。
使用scoped_ptr,不要使用auto_ptr,因为auto_ptr的所有权转移语义令人迷惑。
谨慎地使用shared_ptr。
2.cpplint
Google使用cpplint.py脚本来检测style errors。
VI Other C++ Features
1.引用参数
只传递const引用作为参数。
2.函数重载
谨慎使用函数重载,因为重载的多个版本可能让人迷惑。
3.默认参数
不使用默认参数,除非是在.cc文件中的static函数或者是unnamed namespace中的函数,因为它们的使用范围很小。
可以使用默认参数模拟变长参数列表。
4.变长数组和alloca()
不使用变长数组和alloca()。
alloca()在栈上分配空间,在调用alloca()的函数返回时空间将被自动释放,因此不能用free()来释放。
使用scoped_ptr和scoped_array来代替。
5.友元
谨慎使用友元,通常用于构建工厂模式。
6.异常
不使用C++异常。
7.运行时类型信息RTTI
不使用RTTI。
通过typeid或者dynamic_cast可以得到C++对象的类型信息。如果需要使用RTTI,那么通常情况下说明你的设计存在问题。
8.Casting
不要使用C风格的类型转换。
9.Streams
不要使用流。
10.Preincrement and Predecrement
使用前缀++和--,因为后缀形式会发生一次临时对象的生成和拷贝。
11.const的使用
在适宜的地方尽量使用const。const声明可以让代码更具可读性,并且可以在编译期检测到可能存在的Bug。
如果传递引用参数,则应该总是const引用。
任何时候如果可以就应该将方法声明为const。Accessors应该总是声明为const。
如果一个数据成员在构建以后就不会再改为,则应该声明为const。
12.整型
C++对整型的长度没有做出具体规定。在所有的整型中我们应该只使用int,其它长度的整型应使用<stdint.h>中具有明确长度定义的类型,如int32_t。
int应该使用在一些我们知道数值不会变得太大的场景,如循环计数器。尽可能使用标准类型size_t和ptrdiff_t。
不要使用无符号整型表示一个不可能为负的数,使用断言来检查它。
只在两种情况下使用无符号整型:表述一个位模式(进行位计算),有符号整型确实不够大。
使用无符号整型表示无符号数,如果用户传入负数时会发生类型转换,并且编译器可能不会给出警告,这可能造成难以发现的Bug。
13.64-bit Portability
代码应在32位平台和64位平台具有良好的可移植性。需要考虑3个问题:printing, comparisons, and structure alignment。
printf()规定的格式描述符没有明确指明变量的长度,使用<inttypes.h>中定义的PRI*宏来替代。
记住sizeof(void *) != sizeof(int),如果需要一个和指针长度相同的整型,使用intptr_t。
注意结构体的对齐,可以使用__attribute__((packed))控制结构体的对齐方式。
使用LL和ULL后缀来创建一个64位常量。
如果确实需要为32位和64位编写不同的代码,使用#ifdef _LP64来区分不同代码,但是你应该尽量避免这样做。
14.Preprocessor Macros
谨慎使用宏,尽可能���用内联函数,枚举和常量来替代。
使用宏时遵循如下规则可避免很多问题:
不要在.h文件中定义宏。
在即将使用到宏的地方再定义宏,使用完以后立刻#undef。
不要仅仅因为要定义你自己的版本而#undef一个已经存在的宏,你应该为宏选择一个唯一的名字。
不要使用##(在宏定义中用于连接字符串)来生成函数/类/变量名。
15.0 and nullptr/NULL
整型使用0,浮点使用0.0,字符使用'\0',指针使用nullptr (in C++11)或者NULL。
16.sizeof
使用sizeof(varname)而不是sizeof(type)。这样当变量类型发生变化时不用修改相关的sizeof代码。
17.auto
C++11可以使用auto关键来定义一个变量,变量的类型将根据它的初始化表达式进行推导。使用auto表达式可以避免重复书写很长的类型名,比如STL中的迭代器类型。
只将auto关键字用于局部变量。
18.Boost
只使用Boost库中approved的部分。
19.C++11
只使用C++11 (formerly known as C++0x)中approved的特性。