编写高质量代码_改善C++程序的150个建议 读书笔记
这几天看了下这本书《编写高质量代码_改善C++程序的150个建议》,觉的蛮有收获的,再次记录下自己以前不清晰的知识点,以供学习。
编写符合标准的main函数
C语言标准规定了main
函数的两种标准形式:
int main(void)
int main(int argc, char* argv[])
C++03标准中给出了两种main
函数的定义方式
int main()
int main(int argc, char *argv[])
上述标准都要求main
函数返回一个整型数值
extern C的作用
extern C
的作用是当程序被C++编译器编译时,让后续的链接器以C方式来寻找函数,方便C++程序调用C程序。
注释风格
C++风格注释形如:// ....
,推荐使用这样的注释。但是,头文件说明和函数默认参数的注释,还是用C风格(/**/
)的较好。
不要写与编译器依赖紧密的代码
例如:printf("a %d %d", p(), q())
,p和 q函数的执行前后顺序与编译器实现相关,应当避免此类代码。类似的还有 c = p() * q() * r()
。
尽量用const,enum,inline代替#define
inline
关键字用在函数调用展开,在类声明中定义并且实现的函数自动为内联函数。如果需要将其他函数定义为内联函数,则需要在函数实现头声明此关键字,才让编译器尝试去内联,至于具体是否内联,还要求函数体满足一定条件才行,总体原则是短小精悍。
在使用define的场合,注意用()来保护宏函数。例如:#define MAX(a, b) ((a) > (b) ? (a) : (b))
struct在C和C++下的异同
C语言的struct不允许定义函数程序,而C++语言下的struct可以。
类
成员初始化
const成员变量只能通过"成员初始化列表"来进行初始化。在C++中允许声明一个函数但不无函数体实现。
类中成员变量如果是自定义类型,那么它的构造是先于类本身的构造函数,其构造顺序与在类中定义的顺序一致。
RAII手法:将资源的申请和释放操作包裹到类中的构造和析构函数中,隐式地保护资源。
所有数据成员一律为private类型。如果派生类需要用到,那么在用到的时候再将其改为protect类型,否则,一律声明为private类型,对外隐藏。
在具体声明时,可以按类型来多段声明,比如私有控件,来一个private,私有数据来另外一个private。
派生类初始化
在继承体系中,对象的构造是从最根处开始的。
虚函数相关
当类中至少包含一个虚函数时,才需要将其析构函数设置为虚函数。
在派生类被正确地构造出来之前,调用派生类的虚函数是没有意义的。
不要在构造/析构函数中调用虚函数。
虚函数需要配合继承机制,它生效于运行期,属于OOP范畴,适用于接口相同,逻辑不同的代码应用于不同的场合,实现了接口与实现的分离。
在C++中,还有一种成为pimpl idiom
的手法,来更加彻底的分离接口和实现,大体实习思路是将对象中全部的数据成员封装在另一个实现类中,在本类中通过类成员指针或者是智能指针来引用。对外暴露的接口在内部都转发给实现类处理。
模板在编译器生效,它的本意是让不同类型的数据作用在相同的代码逻辑,属于GP范畴,体现为算法的普适性。
基类中虚函数的保护级别:
- protect:标明派生类一定要重写该虚函数
- private:标明派生类可以修改基类虚函数,如果无特殊要求的话,可以复用基类虚函数
- public:禁止将虚函数声明为public权限
接口设计
设计目标:能覆盖完整功能并且达到接口最小化
以行为为中心的类设计,对外的public函数放在前面,需要继承的protect虚函数紧随其后,再后面是private的虚函数、普通函数以及成员变量。
语法的背后含义是语义,接口设计要有明确的语义,不可模棱两可、职责不清。
拷贝相关
如果类中无动态分配的资源类型,那么默认的浅拷贝可以满足要求。一旦有指针类型,那么,就必定有动态申请的资源,在拷贝时,需要重写赋值运算符,在目标类中重新申请空间,并且将原动态资源内容原封不动的拷贝过来。
如果重写派生类的赋值操作符,那么需要调用基类的赋值操作符,完整的进行赋值,而不是只赋值派生类数据,不赋值基类数据。
一般,只有在类宏成员变量动态申请了资源,才需要重写赋值操作符和拷贝构造函数。考虑到A = A
这种情况,还需要重写==
操作符,一般来说,重写的==
,那么!=
也顺带写上,保持相关操作的一致性。
A& A::operator=(const A& rhs)
{
if(this == &rhs)
return *this;
parent::operator=(rhs)
// 派生类成员赋值
return *this;
}
重载相关
overloading:具有相同函数名,但是不同参数形式的函数集合
overriding:在类中,派生类重写基类提供的虚函数,建议使用 override
关键字来声明,在派生类中,不要用virtual
来标明此为虚函数。
Hiding:派生类中重写和基类一样签名的非虚函数
模板相关
模板的声明和定义需要放在同一个头文件中,如果分离声明和实现,则外部在使用时,需要包含实现的cpp文件,这违反惯用法。
.inl文件可以将模板头文件和复杂模板定义分离,一般将其在头文件尾部进行包含。
异常相关
如底层发生异常,则需要逐级上报,直到有能力处理此异常的层级来处理。如果程序都没处理,则会被C++系统捕获并终止程序运行。异常可以将发生错误和处理错误分离。
一般以传值来抛出异常,以 const 引用来捕获异常,不涉及到异常对象的清理工作,无对象切割问题,如本层级处理后还需要继续抛出异常,可调用throw来。
智能指针
优先使用shared_ptr
,它内部工作原理是引用计数,线程安全,支持扩展,推荐使用。
不要在stl容器中存储auto_ptr对象,因为容器元素是允许拷贝构造和赋值的,而auto_ptr是不能满足这一条件的。
STL相关
多用算法调用,少用手写循环。
杂项优化
不要返回局部变量的引用。
双重循环时,短循环放在外面,长循环放在里面。
优化常规框架:profiling --> 算法 --> 数据结构 --> 实现细节。
代码的主要功能是供给别人阅读,其次才是让编译器编译,让计算机执行,除了修改代码外,还有很多不用修改代码就能带来的优化,作为一个有追求的程序员,多了解代码之外的优化和设计方法是很有必要的。