条例26
尽可能延后变量定义式出现的时间
- 尽可能的延后定义一个变量,因为只要你定义出来哪怕你没有使用,也得承受构造和析构成本。假如有异常抛出的情况,你定义类一个变量还没有使用的时候就抛出了异常。这个变量就直接调用了析构,做了无用功。应当尽可能的延后到使用前一刻为止。例如用拷贝构造代替构造+赋值。
- 当遇到for循环的时候,可以将定义放在循体外面。因为多视情况下,n次赋值的情况要好于n次构造。若除非已知构造+析构的成本小于赋值的成本,则都应该遵守这种情况
总结
- 尽可能延后变量定义式的出现,这样做能增加效率和可读性
条例27
尽量少做转型动作
-
有两种旧式类型转换,和四种新式类型转换
cpp(T)expression //旧式类型转换 T(expression) const_cast<t> //新式类型转换 dynamic_cast<t> reinterpret_cast<t> static_cast<t>
const_cast 用于将对象的常量性去处,dynamic_cast用于安全向下转型。(一般用于父类对象向子类转换)reinterpret_cast我叫他万能的强制类型转换,不具备可移植性,static_cast,用来强迫隐式转换,是非多态类型的转换(静态转换),编译器隐式执行的任何类型转换都可用static_cast。
-
尽量使用新式类型转换,因为更有辨识度,更具有安全性,编译器更容易发现代码的错误。
转型动作其实有可能包含代码
- 比如int转换成float,因为双方在内存的存储方式不同,所以势必会产生代码。
- 尤其是当父类指针指向子类对象的时候,由于偏移量的存在,所以会在运行期间来回跳转
转型有可能导致错误
- 由于转型动作有可能会产生临时变量,若不注意就有可能对临时变量进行了操作,就会导致伤残。
dynamic_cast的使用场景
- dynamic_cast的执行速度本身是很慢的,成本很高,使用时应注意。
- 通常在拥有一个子类对象,却拥有一个父类指针的时候使用这个转型。
- 我们可以通过两种方法解决这种场景,首先可以通过智能指针操作,这样就绕过了接口而使用智能指针进行操作。或者在父类内提供一个接口,将virtual函数向继承体系上方移动。
绝对避免连续的dynamic转型
- 效率极低,当有这种场景应该用虚函数解决
总结
- 尽量避免转型,注重效率时尤其避免dynamic转型
- 若转型是必要的应将其隐藏在函数背后,而不用手动调用
- 尽量使用新式的类型转型
条例28
尽量避免返回引用指向对象内部成分
- 不能使用引用返回函数体内的局部变量,因为局部变量会被销毁,这样引用就空悬了
- 若使用引用返回private成员,则会破坏原有的封装性。
- 不仅仅是引用,上述两种情况在指针返回或是迭代器返回也是存在的。
- 可以同过返回const 引用保持封装性
总结
- 避免返回引用,指针,迭代器,指向对象内部。这样能增加封装性,避免虚调的发生
条款29
为异常安全而努力是值得的
- 通常有异常安全问题的函数会导致,资源泄漏或者数据败坏(例如指针悬空等问题)。
- 异常安全函数提供以下三种保证,基本承诺 (异常抛出后任何事物仍然保持有效状态,但整体不可预期),强烈保证 (若程序抛出异常,程序会恢复到调用 函数之前的状态),不抛出异常的保证(承诺绝不抛出异常)
- 通常情况下应提供能提供的的最强烈的保证,但最常用的是强烈保证和基本承诺两种。
- 可以采用资源管理类实现异常安全。或者将更改元素的操作语句放在有可能出现异常语句的后面,这样即使抛出异常也不会改变原来的值。
通过,采用拷贝复制策略(为打算操作的对象创建一个副本,然后对副本进行一切修改,修改操作成功后再拷贝回给原始的对象)。可以很好的实现强烈保证要求的复原操作
- 通常拷贝复制策略是通过pimpl 手法(将所有隶属对象的数据从原对象放进另一个对象(副本)内,然后赋予原对象一个指针(指向副本))实现的。但这个策略无法保证强烈保证,因为假设你在这个策略内调用了别的函数,并且这个函数本身不是的异常安全性低于强烈保证,这时我们就需要写出代码恢复此函数调用之前的状态。就算该函数是强烈保证也不行,假如调用两个函数,一个正常执行一个抛出异常,就会造成部分更改的问题。
- 综上,有的时候实现强烈保证有时候是不实际的,因为本身拷贝复制策略效率就很低。这时就只能提供基本保证。
总结
- 异常安全函数即使发生异常也不会泄漏资源或者允许任何数据结构破坏,通常有三种类型的保证:基本型,强烈性,不抛异常性。
- 强烈保证往往能够以拷贝复制实现,但强烈保证并非对所有函数都具备可实现意义。
- 函数提供的保证级别是由所调用函数的最低等的异常安全级别决定的
条例30
透彻了解内联函数
- 内联函数的执行动作像函数,同时又不用承受调用函数的开销,比宏好很多。
- 过度使用内联会导致代码体积增大,降低指令高速缓存装置的击中率,带来效率降低。
- 内联只是对编译器的一个建议,是否实现成内联是由编译器自身决定。对于所有虚函数都无法实现成内联。因为虚函数意味这运行时决定调用哪个函数,而内联需要在编译期间替换出函数本体。
- 大多数内联是在编译器间行为。不是所有类模板都需要定义为内联。
- 构造和析构往往不适合实现成内联。虽然你以为构造和析构不含代码适合内联,但实际上为了确保资源的正确构造和析构,同时维护异常安全性,编译器会在你的构造和析构函数里加上代码来实现这些功能,这就让他们不适合内联。
- 内联函数意味着不能随着库函数的升级而升级,一旦有修改必须重新编译链接。
总结
- 将大多数内联限制在小型和被频繁调用的函数身上,这样能使调试更加容易,让潜在的代码膨胀问题最小化,提升程序运行速度。
- 不能因为类模板出现在头文件而将其声明为内联。