大多数情况下,适当提出拟的类定义以及函数声明,是花费最多心力的两件事。尽管如此,还是有很多东西需要小心:太快定义变量可能造成效率上的拖延;过度使用转型(casts)可能导致代码变慢又难维护,又招来微妙难解的错误;返回对象“内部数据之号码牌(handls)”可能会破坏封装并留给客户虚吊号码牌;为考虑异常带来的冲击则可能导致资源泄漏和数据败坏;过度热心地inlining可能引起代码膨胀;过度耦合则可能导致让人不满意的冗长建置时间。
条款26:尽可能延后变量定义式的出现实现时间
“尽可能延后”的意义有两层:
1.延后变量的定义,直到非得使用该变量的前一刻为止。否则在变量定义与使用之间的代码可能抛出异常,那么变量的构造和析构成本浪费。
2.应该尝试延后这份定义直到能够给他初值参数为止。这样不仅能避免构造(和析构)非必要对象,还可以避免无意义的default构造行为。因为“通过默认构造函数构造出一个对象然后对他赋值”比“直接在构造时指定初值”效率低。
如果变量在循环内使用考虑一下情况:
//方法A:定义循环外
Widget w;
for (int i = 0; i < n; ++i)
{
w = some value dependent on i;
...
} //1个构造函数+1个析构函数+n个赋值操作;
//方法B:定义循环外
for (int i = 0; i < n; ++i)
{
Widget w(some value dependent on i);
...
} //n个构造函数+n个析构函数
做法A: 1个构造函数+1个析构函数+n个赋值操作
做法B:n个构造函数+n个析构函数
此外,做法A作用域比B大,有时对程序可理解性和易维护性造成冲突。
因此,除非:1.你知道赋值成本比“构造+析构”成本低;2.你正在处理代码中效率高度敏感的部分,否则应该使用方法B。
请记住:
尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。
条款27:尽量少做转型动作
C++提供了四种新式的转型
A:const_cast通常被用来将对象的常量性转除。它也是唯一有此能力的C++ style转型操作符。
B:dynamic_cast主要用来执行“安全向下转型”,也就是用来决定某些对象是否归属于集成体系中的某个类型。它是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。
C:reinterpret_cast意图执行低级转型,实际动作可能取决于编译器,这也就表示它并不可移植。
D:static_cast用来强迫隐式转换,例如将non-const对象转换成cosnt对象,将int转换成double,将void*指针转为typed 指针,将pointer-to-base转为pointer-to-derived等等。
新式转型比较受欢迎:1,很容易在代码中被辨别出来;2,各种转型动作愈窄化,编译器愈能诊断出错误的运用。
唯一使用旧式转型的时机是当调用一个explicit构造函数将一个对象传递给一个函数时,如下:
class Widget{public:
explicit Widget(int size);
...
};
void doSomeWork(const Widget &w);
doSomeWork(Widget(15));//函数风格的转型动作创建一个Widget
doSomeWork(static_cast
蓄意的“对象生成”动作不怎么像“转型”,很可能使用函数风格的转型动作,而不使用static_cast。但有时最好忽略你的感觉,始终理智的使用新式转型。
任何一个类型转换(不论是通过转型动作而进行的显示转换还是通过编译器完成的隐式转换)往往真的令编译器编译出运行期间执行的码。例如在这段程序中:
int x, y;
...
double d = static_cast
将int 转换成double几乎肯定会产生一些码,因为大部分计算机体系结构中,int的底层表述不同于double的地层表述。下面这个例子可能让你稍微瞪大眼睛:
class Base{...};
class Derived : public Base{...};
Derived d;
Base* pb = &d; //隐喻地将derived*转换成Base*
这里我们只是建立一个base class指针指向一个derived class对象,但有时候上述的两个指针值并不相同。这种情况下会有个偏移量在运行期被施行于Derived*指针身上,用于取得正确的Base*指针值。
上述例子表明,单一对象(例如一个类型为Derived的对象)可能拥有一个以上的地址(例如“以Base*指向它”时的地址和以“Derived*指向它”时的地址
)。c,java,c#不可能发生这种事,但c++可能!实际上一旦使用多重继承,这事几乎一直发生着。即使是在单一继承中也可能发生。虽然这还有其他意涵,但至少意味着你通常应该避免做出“对象在c++中如何布局”的假设,更不应该以此假设为基础执行任何转型动作。例如将对象地址转型成char*指针,然后在他们身上进行指针运算,几乎总是导致无意义(不明确)行为。
对象的布局方式和他们的地址计算方式随编译器的不同而不同,那意味着“由于知道对象如何布局”而设计的转型,在某一平台行的通,在其他平台并不一定行得通。
另一件关于转型的有趣事情是:我们很容易写出某些似是而非的代码(其他语言中也许是对的)。例如SpecialWindow的onResize被要求首先调用Window的
onResize。下面是看起来对,实际上错:
class Window{
public:
virtual void onResize(){...}
...
};
class SpecialWinddow:public Window{
public:
virtual void onResize(){
static_cast
...
}
};
它调用的并不是当前对象上的函数,而是稍早转型动作所建立的一个“*this对象的base class成分”的暂时副本身上的onResize!并不是在当前对象身上调用Window::onResize之后又在该对象上执行SpecialWindow专属行为。不,它是在“当前对象之base calss成分”的副本上调用Window::onResize,然后在当前对象上执行SpecialWindow专属动作。如果Window::onResize修改了对象内容,当前对象其实没被改动,改动的是副本。然而SpecialWindow::onResize内如果也修改对象,当前对象真的会被改动。这使当前对象进入一种“伤残”状态:其base class成分的更改没有落实,而derived class成分的更改倒是落实了。
解决之道是拿掉转型动作,代之你真正想要说的话。所以,真正的解决方法是:
class SpecialWinddow:public Window{
public:
virtual void onResize(){
W