条款26:尽可能延后变量定义式的出现时间
有些对象,你可能过早的定义它,而在执行的过程中发生了导常,造成了开始定义的对象并没有被使用,而付出了构造成本来析构成本。
所以我们应该在定义对象时,尽可能的延后,甚至直到非得使用该变量前一刻为止,应该尝试延后这份定义直到能够给它初值实参为止。
这样做的好处是:不仅可以避免构造(析构)非必要对象,还可以避免无意义的default构造行为。
遇到循环怎么办?此时往往我们会有两个选择:
做法A:1个构造函数+1个析构函数+n个赋值操作 // 在循环外面定变量,在循环内赋值
做法B:n个构造函数+n个析构函数 // 在循环内定义并初始化变量
这时候要估计赋值的成本低还是构造+析构的成本低,另外值得考虑的是对象作用域的问题。
条款27:尽量少做转型动作
转型语法通常有三种不同的形式:
1,C风格的转型动作:(T)expression
2,函数风格的转型动作:T(expression)
3,上面的第二种被称为“旧式转型”,C++提供了四种新式转型:
const_cast<T>(expression) //通常用来将对象的常量性转除
dynamic_cast<T>(expression) // 转换为子类,用来决定某对象是否归属继承体系中的某个类型,但是耗费重大运行成本
reinterpret_cast<T>(expression) // 执行低级转型,实际动作取决于编译器,这也就表示它不可移植。如将一个point to int 转型为一个int
static_cast<T>(expression) // 强迫隐式转换
在很多派生类的设计中,派生类的virtual函数需要去调用基类的virtual函数,下面是一个例子,window是一个基类,它定义了一个虚函数onResize,而Special Window是一个派生类。
class Window { public: virtual void onResize(); }; class SpecialWindow :public Window { public: virtual void onResize() { static_cast<Window>(*this).onResize(); // 将*this转型为Window,然后调用其onResize // ... SpecialWindow专属动作 } };
但是代码中的相通过转型来调用基类的virtual函数,事实上static_cast<Window>(*this).onResize()调用的是一个*this的基类成份的一份拷贝的onResize函数,所以onResize操作所能影响到的成员只属于一个临时对象。解决的办法是把转型拿掉即可,替换成Window::onResize()。
请记住:
如果可以,尽量避免转型,特别是在注重效率的代码中避免dynamic_cast。如果有个设计需要转型动作,试着发展无需转型的设计。
如果转型是必需要,试着将它隐藏于某个函数背后。客户随后可以调用该函数,而不需将转型放进他们自己的代码。
宁可使用C++style(新式)转型,不要使用旧式转型,前者很容易被辨识,而且也比较角着分门别类的职掌。
条款28:避免返回handles指向对象的内部成分
class Point { public: Point(int x, int y); void SetX(int newVal); void SetY(int newVal); private: int x_cor; int y_cor; }; struct RectData { Point ulhc; // 矩形左上角的点 Point lrhc; // 矩形右上角的点 }; class Rectangle { private: shared_ptr<RectData> pData; public: Point& upperLeft()const{ return pData->lrhc; } Point& lowerRight()const{ return pData->ulhc; } };
上面的代吗中Point是表示坐标系中点的类,RectData表示一个矩形的左上角与右下角点的点坐标。Rectangle是一个矩形的类,包含了一个指向RectData的指针。
我们可以看到了uppLeft和lowerRight是两个const成员函数,它们的功能只是想向客户提供两个Rectangle相关的坐标点,而不是让客户修改Rectangle。但是两个函数却都返回了references指向了private内部数据,调用者于是可以通过references更改内部数据。
这给了我们一些警示:成员变量的封装性只等于“返回其reference”的函数的访问级别;如果const成员函数传出一个reference,后者所指数据与对象自身有关联,而它又被存储于对象之外,那么这个函数的调用者可以修改那笔数据。
handles(号码牌,用于取得某个对象)指reference、指针和迭代器,它们返回一个“代表对象内部数据”的handle。
我们可以对上面的成员函数返回类型上加上const来解决问题:
public: const Point& upperLeft()const{ return pData->lrhc; } const Point& lowerRight()const{ return pData->ulhc; }
但是函数返回一个handle代表对象内部成分还总是危险的,因为可能会造成dangling handles(空悬的号牌)。比如某个函数返回GUI对象的外框(bounding box)。
class