读书笔记中涉及到的所有代码实例可通过https://github.com/LuanZheng/EffectiveCPlusPlus.git进行下载获得。
条款26 尽可能延后变量定义式的出现时间
延后变量定义式的出现时间,直到确实需要它。万一在后续运行过程中,出现异常,该变量可能并没有实际使用,因此延后定义可以节省构造成本和析构成本。
甚至应该尝试延后这份定义直到能够给它初值实参为止。如果这样,不仅能够避免构造和析构非必要对象,还可以避免无意义的默认构造行为。
若变量在循环体内,除非
(1)你知道赋值成本比“构造+析构”成本低;
(2)你正在处理代码中效率高度敏感部分;
否则,应选择将变量定义在循环内。
Item27 尽量少做转型动作
C风格的转型动作看起来像这样:
(T)expression //将expression转型为T
函数风格的转型动作看起来像这样:
T(expression) //将expression转型为T
两种形式并无差别。
C++还提供四种新式转型
const_cast<T>(expression) //用来将常量性转除
static_cast<T>(expression) //强迫隐式转换,但无法用来将常量性转除
dynamic_cast<T>(expression) //用来执行“安全向下转型”,也就是用来决定某对象是否归属继承体系中的某个类型。
reinterpret_cast<T>(expression) //低级转型,很少见
推荐使用新式转型。第一,它们很容易在代码中被辨识出来。第二,各转型动作的目标愈窄化,编译器愈可以诊断出错误的运用。
任何一个类型转换(不论是通过转型操作而进行的显示转换,或通过编译器完成的隐式转换)往往真的令编译器编译出运行期间执行的码。
单一对象(例如一个类型为Derived的对象)可能拥有一个以上的地址(例如“以Base*指向它”时的地址和”以Derived*指向它”时的地址)。
另一种在使用转型的情况下,我们很容易写出某些似是而非的代码。比如下面这个例子:
在基类(Window)onResize中,将Height赋值为8,在派生类(specialWindow)中,将Color赋值为6.
如果使用转型的写法,会导致Height没有被赋值,而Color赋值为6. 原因在于转型生成了新的基类对象副本。
void SpecialWindow::onResize()
{
std::cout << "SpecialWindow::onResize() called." << std::endl;
//static_cast<Window>(*this).onResize(); //错误,该转型调用的不是当前对象上的函数,会生成当前对象之base class副本
Window::onResize(); //正确
m_Color = 6;
}
而对于dynamic转型,常常效率比较低下。之所以需要dynamic_cast,通常是想在派生类对象上执行派生类函数,而手上却只有指向基类的指针或引用。有两个一般性的做法可以解决这一问题。
第一,使用容器并在其中存储直接指向派生类对象的指针。
第二,通过让base class接口处理“所有可能之各种派生类”,那就在base class中提供virtual函数做你想对各个派生类想做的事。
如果转型是必要的,通过设计将它隐藏在某个函数背后。不要暴露在接口上。
例子见Item27
Item28 避免返回handles指向对象内部成分
第一,成员变量的封装性最多只等于“返回其reference”的函数访问级别。(因为可在外部修改了)
第二,如果const成员函数传出一个reference,后者所指的数据与对象自身有关联,而它又被存储与对象之外,那么这个函数的调用者可以修改那笔数据。
我们要留心不要返回对象内部成分的handles,一旦某个handle被传出去了,就暴露在“handle比它依附的对象更长寿”的风险下。