:
allocator>classTCircularQueue;
classIDevVideoEnc;
3.5宏
全部使用大写字母,单词之间用下划线分开。
给代码控制宏命名加上前缀,来分类不同用途的宏。
以上都是指自定义的宏,不包括编译预定义宏。
#defineJSON_VALUE_USE_INTERNAL_MAP1
#defineJSON_USE_CPPTL1
3.6常量
避免使用常量宏,不得不使用时,参考宏的命名规则。
const,枚举值等常量,应尽量作为类的成员,命名方式和变量相同。
#defineMAX_SIZE10//notsogood
classA
{
enum
{
maxSize=10
};
}
4设计规则
4.1识别类和函数
基本原则是高抽象性,高内聚性,低耦合性。
使类的接口完整并且最小。
一个函数不要完成多个功能。
一个变量也不能有多用途。
把方法中的重复代码抽象成私有函数。
尽量使用已有的函数或者标准库来实现新的函数。
区分两个类“A是一个B”与“A是B的一部分”的关系,分别对应类的继承和聚合关系。
//正确的设计,虽然代码冗长。
classHead
{
public:
voidLook(void){m_eye.Look();}
voidSmell(void){m_nose.Smell();}
voidEat(void){m_mouth.Eat();}
voidListen(void){m_ear.Listen();}
private:
Eyem_eye;
Nosem_nose;
Mouthm_mouth;
Earm_ear;
};
如果允许Head从Eye、Nose、Mouth、Ear派生而成,那么Head将自动具有Look、Smell、Eat、Listen这些功能。
示例十分简短并且运行正确,但是这种设计方法却是不对的。
//功能正确并且代码简洁,但是设计方法不对。
classHead:
publicEye,publicNose,publicMouth,publicEar
{
};
4.2构造函数
如果一个类可能有多个构造函数,应有公用的私有初始化函数对成员进行初始化。
如果确定只有一个构造函数,应使用成员初始化列表进行初始化。
并且初始化列表中成员列出的顺序和它们在类中声明的顺序相同。
使用explicit关键字消除隐式转换
classCString
{
//…
explicitCString(intn);//preallocatenbytes
CString(constchar*p);
};
CStrings1=‘a’;//error:
noimplicitchar->CStringconversion
CStrings2(10);//ok:
stringwithspacefor10characters
将采用了单件实例的类或接口的构造函数和析构函数权限设置为private或protected
classITimerManager
{
public:
staticITimerManager*instance();
protected:
ITimerManager();
virtual~ITimerManager();
…
4.3析构函数
确定基类有虚析构函数。
4.4拷贝函数与赋值函数
为非POD类型(plain-old-data,onlyints,chars,floats,orpointers,orarrays/structsofPOD)类声明一个拷贝构造函数和一个赋值操作符。
而且在operator=中,应返回*this的引用,对所有数据成员赋值,检查给自己赋值的情况。
classCZString
{
public:
CZString(constchar*cstr);
CZString(constCZString&other);
~CZString();
CZString&operator=(constCZString&other)
{
if(this==&other)return*this;
CZStringtemp(other);
constchar*cstr=m_cstr;
m_cstr=temp.m_cstr;
temp.m_cstr=cstr;
}
private:
constchar*m_cstr;
}
4.5封装性
不要重新定义继承而来的非虚函数、成员、函数的缺省参数值。
避免出现public数据成员。
单件模式对象构造函数应为私有类型。
如果一个操作不会修改对象的属性,应该加const修饰,妥善处理const的传递性。
将只与类有关的常量、类型的定义放在类的内部。
classA
{
public:
intgetValue()const;
private:
intvalue;
classInfo
{
};
}
4.6函数参数
如果参数是一个对象,应改成传递其引用或者指针。
如果参数是指针或引用,且仅作输入用,则应在类型前加const,注意值传递不用修饰。
sizt_tstrlen(constchar*string);
CRect(constCPoint&point,constCSize&size);
不能计算指针形参对应的实参数组的大小(已退化)。
函数的参数顺序应该是先输入参数,再是输出参数,同时数组的地址在前,长度在后。
4.7函数返回值
不要返回栈对象的指针或引用,不要返回栈数组。
Object*createObject()
{
Objectobject;
return&object;
}
设计函数时,必须返回一个对象时不要试图返回一个引用。
Object&createObject()
{
Objectobject;
returnobject;
}
Object&createObject()
{
Object*object=newObject;
return*object;
}
4.8契约
提供约束,宁可编译和链接时出错,也不要运行时出错。
检查函数的前置条件,满足前置条件是调用者的责任,而被调用者假定它的前置条件已经满足。
检查函数的后置条件,也就是函数返回之时哪些条件是调用者可以期望的。
检查类的不变式,类的不变式确保类处于良好的状态中,一般提供一个成员函数如isValid()在函数进入和退出时检查不变式。
对于运行契约检查,一般使用assert,也可以采用日志记录,抛出异常等方式。
///获取队首元素
constT&front()
{
assert(m_size>0);
returnm_queue[m_front];
}
不要混淆运行契约与有效代码,运行契约仅检查参数的合法性,且不应修改契约外定义的变量。
assert(--m_count>0);
4.9规模
函数参数个数尽量控制在5个之内。
函数代码尽量控制在200行代码之内。
每个类的平均方法数尽量控制在20个之内。
函数嵌套深度控制在6级之内,减少没必要的递归嵌套。
函数(调度函数除外)扇出控制在5个以内。
4.10类型转换
避免强制类型转换。
避免隐藏类型转换。
4.11常量
C++中使用const来定义常量,替换宏定义的常量。
不能使用无意义的立即数。
类中使用的常量应使用类的内部定义的枚举类型的值。
只能使用ascii字符的字符串常量,不能使用中文等特定语言的字符串常量。
4.12内联
C++中应该用内联函数替换宏定义的代码段。
对性能要求比较高的场合应使用内联函数。
使用C++标准库常用的内联函数,比如max,min等。
如果使用某函数的地方较多,而且函数体较大,不应使用内联函数。
4.13解耦合
解耦合设计到很多方面,包括模块的划分,头文件依赖,模块对外的接口必须有解耦合处理,隐藏内部实现的细节,避免编译依赖。
如果可以使用对象的引用和指针,就要避免使用对象本身。
定义某个类型的引用和指针只会涉及到这个类型的声明。
定义此类型的对象则需要类型定义的参与。
尽可能使用类的声明,而不使用类的定义。
因为在声明一个函数时,如果用到某个类,是绝对不需要这个类的定义的,即使函数是通过传值来传递和返回这个类。
不要在头文件中再包含其它头文件,除非缺少了它们就不能编译。
相反,要一个一个地声明所需要的类,让使用这个头文件的用户自己去包含其它的头文件,以使用户代码最终得以通过编译。
使用句柄类(Handleclass)隐藏实现细节来实现解耦合。
//编译器还是要知道这些类型名,因为Person的构造函数要用到它们
classDate;
classAddress;
classPersonImpl;
classPerson
{
public:
Person(conststring&name,constDate&birthday,constAddress&addr);
virtual~Person();
stringname()const;
stringbirthDate()const;
stringaddress()const;
private:
PersonImpl*impl; //指向具体的实现类
};
除了句柄类,另一选择是使Person成为一种特殊类型的抽象基类,称为协议类(Protocolclass)。
和句柄类的用户一样,协议类的用户只是在类的接口被修改的情况下才需要重新编译。
classPerson
{
public:
virtual~Person();
virtualstringname()const=0;
virtualstringbirthDate()const=0;
virtualstringaddress()const=0;
virtualstringnationality()const=0;
};
4.14可重入
对于可能被多个任务访问的资源,要使用互斥量保护,上层应用不应直接使用中断。
保护的范围要准确,一般使用不同的互斥量来保护不同的资源,如果整体的代价不高,也可以使用同一个互斥量。
仔细考察资源之间依赖的情况,防止死锁。
使用Guard(守卫者)来实现函数内的保护,除非难以使用。
通过逻辑的设计,能够保证多个线程访问同一资源不会发生冲突时,可以不保护。
对于只需要自加和自减的变量可以使用AtomicCount来保证原子性。
5内存管理规则
5.1模块化
使用或者设计专有的内存管理模块,而不是直接使用new/delete。
给标准容器编写高效安全的allocator。
使用智能指针管理内存或对象生命周期。
5.2静态分配
在程序启动时从系统中静态分配好需要持续使用的内存,提高性能。
5.3new/delete
C++中使用new/delete替换malloc/free。
如果重载了operatornew就要同时重载operatordelete。
5.4有效性
使用有效的指针及其所指向的空间,杜绝野指针。
5.5正确释放
明确内存块的所属对象及对象的生命周期,及时释放,防止内存泄露。
new出来的数组释放时也应表明它是数组。
5.6拷贝
数组拷贝,或调用内存拷贝、字符串拷贝接口时,应注意源区域和目标区域重叠的情况,参考memmove函数。
6注释规则
6.1有效性
注释的内容要清楚、明了,含义准确,防止注释二义性。
在代码的功能、意图层次上进行注释,提供有用、额外的信息。
对于已经充分自注释程度的代码无需注释。
6.2普通注释
函数内部使用C++注释风格,并在注释符号和注释内容间留一个空格。
注释内容加在代码对象的上方或右方。
if、for、do、while、case、switch、default等语句应该注释。
未break的case段后面应该注释。
代码块、预处理块较长的,应该注释。
6.3Doxygen注释
函数外部使用DoxygenC++风格注释,并在注释符号和注释内容间留一个空格。
注释由简要注释和详细注释组成,两者都是可选的,且是两者都不能重复。
简要注释使用一行C++注释,并在开始加一个额外的斜杠,使用\brief命令可以支持多行,简要注释的范围将以空行或其他命令结束。
详细注释使用至少两行C++注释,每行开始加一个额外的斜杠。
注释块可以加在代码对象的上面或者右方,如果加在右方,还需要加额外的<符号,只有成员和参数的注释可以加在右边。
一般使用简要注释即可,如需要更详细的说明的可以使