C++规范
让自己习惯C++
条约1:视C++为一个语言联邦
c++目前是一个多重泛型编程语言,支持过程式,对象式,函数式,泛型式,元编程式.这是一个语言联邦
- C part of C++: C++以C为基础
- class part of C++,c with class
- template part of C++,C++泛型编程
- stl part of C++ 标准模板库template的程序库,对容器,迭代器,算法,函数对象有规约与配合协调
条约2:尽量以const,enum,inline代替 #define
- 使用const常量替代宏常量
- 使用enum,使数字成为记号
- 使用inline代替宏函数
条约3:尽可能使用const
- const 变量 将某些东西声明const 可以让编译器侦测错误用法,const 可以被施加于任何作用域内的对象,函数参数,函数返回类型,成员函数本体
- 编译器强制实行 bitwise constness 但你便携程序应使用概念上的常量性.
- const 和non-const 成员函数有实质等价的实现时,令non-const调用const可以避免代码重复
构造/析构/赋值运算
条约4:确定对象被使用前已被初始化
-
为内置型对象手动初始化,C++并不保证初始化它们
-
构造函数最好使用成员初始列,而不要在构造函数本体内使用赋值操作,初值列出的成员变量,排序次序应该与其class声明次序相同
-
为避免跨编译单元初始化次序问题,请以局部静态变量替换非局部静态变量
条约5:了解C++默默编译并调用了哪些函数
空类在C++编译时会声明一个拷贝构造函数,一个拷贝操作符,一个构造函数,一个析构函数,且都为内联函数
条约6:如果不想使用编译器自动生成的函数,就应当明确拒绝
比较优美的方式,是将拷贝构造函数,一个拷贝操作符声明在private环境下,这极大程度的限制了他人调用这种默认函数
毕竟你希望它是独一无二的,更优美的做法是,将其写入基类,再继承,在生成一个类,这样就可以在编译期看到错误
- p驳回编译器暗自提供的机能,可将对应的成员函数声明为private并不予实现,使用uncopyable这样的基类也是一种做法
条约7:为多态基类声明virtual析构函数
- 具有多态性质的基类,应当声明虚拟析构函数,如果类带有任意虚拟函数,就应当具备虚拟析构函数
- 类如果涉及目的不是作为基类使用,或不具备多态性,就不应该声明虚拟析构函数
条约8:别让异常逃离析构函数
- 析构函数绝对不要吐出异常,如果析构函数调用可能抛出异常,析构函数应该捕捉任何异常,并吞下它们或结束程序
- 如果客户需要对某个操作函数运行期间抛出异常做出反应,那么class应该提供一个普通构造函数执行该操作,而不是执行该操作
条约9:绝不在构造和析构过程中调用virtual函数
- 析构与构造期间不要调用虚拟函数,因为此类调用从不下降至派生类,
条约10:令operator = 返回一个ref to *this
- operator = 应返回自己的首地址,a = b = c 采用右结合律,即a = (b = c ),且此条约只是协议,并不强制,但该条约被所有内置类型与stl库l类型(如vector,dequeue,string)所共同遵守,除非你有一个标新立异的好理由,不然还是随众吧
条约11:在 operator = 中处理自我赋值
-
潜在风险为 a[i] = a[j],当i=j 时,或者 *px = *py,当px,py指向同一个对象
-
确保当对象自我赋值时operate= 有良好的行为,其中技术包括"来源对象"和"目标对象"的地址,精心周到的语句顺序,以及copy and swap
-
确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为任然正确
条约12:复制对象时勿忘记每个成分
- 你需要首先调用基类的拷贝构造函数,再将自己的每个成员变量都拷贝出去才可以
- 不要尝试在一个拷贝函数中实现另一个拷贝函数,而应该将其共同的机能放在第三个函数中,并由两个拷贝函数共同调用
资源管理
条约13:以对象管理资源
- 你需要首先调用基类的拷贝构造函数,再将自己的每个成员变量都拷贝出去才可以
- 不要尝试在一个拷贝函数中实现另一个拷贝函数,而应该将其共同的机能放在第三个函数中,并由两个拷贝函数共同调用
条约14:在资源管理类中小心copy 行为
- 复制RAII对象必须一并复制他所管理的资源,所以资源的拷贝行为决定RAII对象的拷贝行为
- 普遍常见的RAII类拷贝行为为:抑制拷贝,施行引用计数法,其他行为也可能被实现
条约15:在资源管理类中提供对原始资源的访问
隐式转换
operator FontHandle() const{return f;}
- apis 往往要求访问原始资源(raw resources),所以每一个RALL class 应该提供一个取得其所管理资源的办法
- 对原始资源访问可能经由显式转换或隐式转换,一般而言显式转换比较安全,但隐式转换对客户比较方便
条约16:成对使用new 和delete 时要采取相同形式
- 如果在new 表达式使用[],那么在delete 中也要使用[].如果不使用,那么delete中也不得使用
条约17:以独立语句将newed 对象置入智能指针
- 以独立语句将newed 对象存储于智能制造内,如果不这么做,一旦一场抛出,可能造成难以察觉的内存泄露
设计与声明
条约18:让接口容易被正确使用,不易被误用
- 好的接口很容易被正确使用,不容易被误用,你应该在你的所有接口中努力达成这些性质
- 促进正确使用 的办法包括接口的一致性,以及与内置类型的行为兼容
- 阻止误用 的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任
- tr1::shared_ptr支持定制类型删除器,这可防范dll 问题,可被用来自动解除互斥锁
条约19:设计class犹如设计type
- 新type 的对象应该如何被创建与销毁,这回影响你的class构造函数与析构函数以及内存分配函数与释放函数,当然前提是如果你打算撰写他们
- 对象的初始化与对象赋值应该有什么样的差别,者会影响到你class 的构造函数与赋值函数操作符的行为,以及者期间的差异,很重要的是别混淆了初始化与赋值,因为它们对于不同的函数调用
- 新type 的对象如果被passed by value(值传递),意味着什么,记住,拷贝构造函数用来定义一个type 的passed by value 该如何实现
- 什么是新 type 的“合法值”?对 class 的成员变量而言,通常只有某些数值集是有效的。那些数值集决定了你的 class 必须维护的约束条件(invariants),也就决定了你的成员函数(特别是构造函数、赋值操作符和所“setter”函数必须进行的错误检查工作。它也影响函数抛出的异常、以及(极少被使用的)函数异常明细列(exception specifications)