01、视C++为语言联邦
C++可分为4个次语言:C、Object-oriented C++、Template C++、STL,在这4者中,C最基本,Object-oriented C++最重要(封装、继承、多态、动态绑定), Template C++(泛型编程)最少用,STL最实用(容器、迭代器、算法)
02、尽量以const,union,inline替换#define
1)宏不能被编译器看到,也没有加入符号表,会给定位、调试带来不便。
2)宏不具有封装性。
03、尽可能使用const
1)迭代器是根据指针塑模出来的,类似T*, 所以用const修饰迭代器,即const (T*),修饰的是指针本身,指针不能改变,而指针指向的内容可以改变。
2)函数返回值前加const,可以避免函数返回被赋值,虽然这样做看起来就很愚蠢,但是为了尽量避免犯错而事先采取措施还是可取的。
3)只有常量性不同的两个成员函数是可以被重载的,这是C++的一个重要特性。
4)const 成员函数调用non-const成员函数是一种错误行为, 而non-const成员函数调用const成员函数才是安全的。
04、确定对象被使用前已初始化
1)变量如果没有被显示初始化,那么其内容是一个随机值,具有不安全性.
2)初始化成员变量的最佳方法是使用初始化列表,这样效率最高,而在构造函数内的属于赋值,而非初始化。
05、了解C++编译器默默编写并调用的函数
1)编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符、以及析构函数。
06、若不想使用编译器自动生成的函数,就该明确拒绝
1)如果不希望编译器自动生成某些函数,可将其声明为private并且不予实现。
07、为多态基类声明virtual析构函数
1)带多态性质的基类,应该声明一个virtual 析构函数,如果class 带有任何virtual 函数,那么它就应该有一个virtual 析构函数。
2)并不是所有基类设计的目的都是为了多态, 比如标准string和STL都不被用作多态,这些函数都不应该有virtual 析构函数。
08、别让异常逃离析构函数
1)析构函数绝对不要向外抛出异常,如果析构函数中可能出现异常,应该捕获它并吞下或终止程序;
2)另一个更好的方法是,将可能出错的部分通过成员函数的方式提供给用户,这样用户可以处理这些异常,如果用户选择放弃调用,再在析构函数中处理。
09、绝不在构造或析构过程中调用virtual函数
1)调用virtual函数的好处是能动态绑定当前类对应的virtual函数,但是如果base class在构造或析构中有调用virtual,当derived class执行构造前,首先执行的是base class的构造,此时由于derived class还没有初始化,C++编译器会对derived class中的virtual函数 视而不见, 也就不会动态绑定到derived class的virtual 函数。所以运行结果别非像预料的那样。
10、令operator= 返回一个 reference this *
重载 运算符函数可以没有返回值,但是考虑到还有连锁运行形式如a = b = c = 3,所以最好还是返回一个指向自身的引用,形如obj &operator=(const obj &rhs){... return *this}
11、在operator= 中处理“自我复制”
obj &operator=(const obj &rhs)
{
delete this.ts;
this.ts = new things(*rhs.ts);
return *this;
}
上述是等号运算符重载函数的主体部分,但是假设rhs 与 this指向的是同一个对象,那么就会出现错误。
第一行delete this.ts,由于this与rhs相同,所以第二行中的rhs.ts其实被delete了,那么此时new出来的对象就有问题了。
所以为了避免在“自我复制”时发生这种悲剧,需要在函数开头判断下this 与 rhs是否相同, 如果相同直接返回this。如 if (this == rhs) return this;。
12、复制对象时勿忘记每一个成分
1)在编写拷贝构造函数和赋值操作符时,不忘遗漏任何成员变量,尤其是在添加下的成员变量后,在拷贝函数里添加。
2)如果不想在拷贝构造函数和复制操作符中写一些重复的代码,可以将这些代码放到一个单独的函数中以供调用,通常命名为init
13、以对象管理资源
1)用申请空间后返回的指针实例化智能类指针,也就是所谓的把资源放进管理对象,那么当类被销毁(一般是退出作用域)时,类的析构函数会自动释放指针指向的空间资源,利用同样的原理也可以用类管理线程锁资源,这样在类被销毁(退出函数,也肯定退出临界区了)时,析构函数自动释放锁,可防止因忘记解锁导致死锁。
2)两个常用的智能锁是auto_ptr 和 shared_ptr, 两者的区别是不能有多个auto_ptr 指向同一个资源, 用拷贝构造或赋值运算符构造一个auto_ptr类时,之前的auto_ptr会指向NULL,而shared_ptr的拷贝构造就较为正常一点,只有在没有shared_ptr指向该资源时才会释放。
14、在资源管理类中小心copy行为
1)如果需要自己实现类似智能指针的时候,例如需要管理锁对象,此时需要考虑copy行为(copy 构造 或 copy 赋值),如果不希望有copy 行为,应显示编写访问权限为private的copy 函数,实现禁用; 你也可以copy其指向的空间,即深度拷贝; 或转移底部资源的拥有权,就想auto_ptr一样;
15、在资源管理类中提供对原始资源的访问
在使用资源管理类的时候,有时也需要访问原始资源,所以需要在管理类中提供访问原始资源的接口,auto_ptr 和 shared_ptr等默认都有get() 成员函数,返回原始资源指针,如果是自己编写的资源管理类,也应该提供类似get() 的功能。
16、成对使用new和delete时要采用相同的形式
如果new表达式中出现了[ ], 那么delete表达式也一定要用[ ]。
17、以独立语句将申请资源返回的指针置于智能指针
18、让接口容易被正确使用,不易被误用
1)尽量保持接口一致性,以及与内置类型的兼容
2)trl::shared_prt支持定制性删除器,可被用来自动解除互斥锁等等。
19、设计class由于设计type
设计class时必须考虑全面、细致
20、宁以 pass-by-reference-to-const 代替 pass-by-value
内置类型、STL的迭代器和函数对象 适用pass-by-value, 除此之外,其他类型建议使用pass-by-reference-to-const,这样更加高效,而且能避免切割问题。
21、必须返回新对象时,别妄想返回其reference
绝不要返回point或reference指向一个函数内部对象。
22、将成员变量声明为private.
1、通过函数访问更具有统一性、封装性。
2、public意味着不封装,而不封装意味着不可改变,灵活性和可扩展性都会变差。
23、宁以non-member、non-friend函数代替member函数
这样做可以增加封装性、包裹性和扩展性。