5 实现
条款26:尽可能延后变量定义式的出现时间
尽可能延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。如果这样,不仅能够避免构造和析构非必要对象,还可以避免无意义的default 构造行为。
循环怎么办??
//定义于循环外 Widget w; for (int i = 0; i < n; ++i){ w = …; } 1个构造函数,1个析构函数,n个赋值操作 | //定义于循环内 for (int i = 0; i < n; ++i){ Widget w; }
n个构造函数,n个析构函数 |
具体取决于一个赋值成本和一组构造+析构成本的大小。
条款27:尽量少做转型动作
旧式转型
C风格:(T) expression
函数风格:T(expression)
新式转型
n const_cast<T>( expression): 移除对象的常量性
n dynamic_cast<T>(expression): 安全向下转型
dynamic_cast can be used only with pointers and references to objects. Its purpose is to ensure that the result of the type conversion is a valid complete object of the requested class.
Therefore, dynamic_cast is always successful when we cast a class to one of its base classes:
1 2 3 4 5 6 7 8 | class CBase { }; class CDerived: public CBase { };
CBase b; CBase* pb; CDerived d; CDerived* pd;
pb = dynamic_cast<CBase*>(&d); // ok: derived-to-base pd = dynamic_cast<CDerived*>(&b); // base-to-derived,pb=null |
The second conversion in this piece of code would produce a compilation error since base-to-derived conversions are not allowed with dynamic_cast unless the base class is polymorphic.
n reinterpret_cast<T>(expression): 低级转型,结果取决于编译器,不可移植
n static_cast<T>(expression): 强迫隐式转换。如non-const到const,int到double,void*到typed*.
static_cast can perform conversions between pointers to related classes, not only from the derived class to its base, but also from a base class to its derived.
一个复杂的转型操作:
class Window{
public:
Window():count(0){
cout<<"Window constructor"<<endl;
}
virtual void onSize(){
cout<<"Window::onSize()"<<endl;
count = 1;
}
int count;
};
class SpecialWindow:public Window { public: SpecialWindow(){cout<<"SpecialWindow constructor"<<endl;} virtual void onSize(){ cout<<"SpecialWindow::onSize()"<<endl; Window::onSize(); cout<<"count = "<<count<<endl; } }; | class SpecialWindow:public Window { public: SpecialWindow(){cout<<"SpecialWindow constructor"<<endl;} virtual void onSize() { cout<<"SpecialWindow::onSize()"<<endl; static_cast<Window>(*this).onSize(); cout<<"count = "<<count<<endl; } }; |
Output: Window constructor SpecialWindow constructor SpecialWindow::onSize() Window::onSize() count = 1 | Output: Window constructor SpecialWindow constructor SpecialWindow::onSize() Window::onSize() count = 0 |
第2份程序将*this转型为Window,对函数onSize的调用也因此调用了Window::onSize。但调用的不是当前对象上的函数,而是稍早转型动作所建立的一样*this对象之base class成分的暂时副本上的onSize,这也是为什么count值不相等的原因。
条款28:避免返回handles指向对象内部成分、
class Point{ public: Point(int x, int y); … void setX(int newVal); void setY(int newVal); };
| struct RectData { Point ulhc; Point lrhc; } | class Rectangle { … private: std::tr1::shared_ptr<RectData> pData; } | |
class Rectangle { … public: Point& upperLeft()const {return pData->ulhc;} Point& lowerRight()const {return pData->lrhc;} } Point c1(0, 0), c2(100,100); const Rectangle rec(c1, c2); rec.upperLeft( ).setX(50); //rec显然不应该是可变的 | class Rectangle { … public: const Point& upperLeft()const {return pData->ulhc;} const Point& lowerRight()const {return pData->lrhc;} } class GUIObject{ }; const Rectangle boundingBox(const GUIObject& obj); GUIObject* pgo; const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft()); 可能出现指针空悬,虚吊 | ||
条款29:为“异常安全”而努力是值得的
异常安全函数提供以下三个保证之一:
n 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态。
n 强烈保证:如果异常被抛出,程序状态不改变。如果函数成功就完全成功,如果失败,程序就回到调用函数之前的状态。
n 不抛掷保证:承诺绝不抛出异常。
class PrettyMenu
{
public: void changeBackgroud(std::istream& imgSrc);
private:
Mutex mutex;
Image* bgImage;
int imageChanges;
};
void PrettyMenu ::changeBackgroud(std::istream& imgSrc)
{
lock(&mutex);
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
unlock(&mutex);
}
替代方案1:
class PrettyMutex
{
std::tr1::shared_ptr<Image> bgImage;
};
void PrettyMenu ::changeBackgroud(std::istream& imgSrc)
{
Lock(&mutex);
bgImage.reset( new Image(imgSrc)); //不需要delete旧图像,这个动作已经由智能指针处理掉了
++imageChanges;
}
替代方案2: copy-and-swap技术
struct PMImpl{
std::tr1::shared_ptr<Image> bgImage;
int imageChanges;
};
class PrettyMenu
{
private: Mutex mutex;
std::tr1::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu ::changeBackgroud(std::istream& imgSrc)
{
Lock(&mutex);
std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));
pNew->bImage.reset( new Image(imgSrc)); //不需要delete旧图像,这个动作已经由智能指针处理掉了
++pNew->imageChanges;
swap(pImpl, pNew);
}
条款30:透彻了解inline的里里外外
inline函数:看起来像函数,动作像函数,比宏好很多,可以调用它们又不需蒙受函数调用所招致的额外开销。
inline会引发代码膨胀,编译器拒绝太过复杂的函数inlining(带有循环或递归),而所有对virtual函数的调用(除非是最平淡无奇的)也都会使inlining落空。
如果某程序要取某个inline函数的地址,编译器通常必须为此函数生成一个outlined函数本体。这意味对inline函数的调用有可能被inlined,也可能不被inlined。
inline void f () {…}
void (*pf) ( ) = f;
f(); //内联
pf(); //不被内联
inline函数无法随着程序库的升级而升级,一旦程序库设计者决定改变f,所有用到f的客户羰程序都必须重新编译,而如果f是non-inline函数,一旦它有任何修改,客户端程序都只需要重新连接就好。
条款31:将文件的编译依存关系降至最低
n 支持编译依存性最小化的一般构想是:相依申明式,不要依于定义式。基于此构想的两个手段是Handle classes和Interface classes。
n 程序库头文件应该以完全且仅有声明式的形式存在,这种做法不论是否涉及template都适用。