一、尽可能延后变量定义式的出现时间
1.跳过毫无意义的default构造过程
//方法1
std::string encrypted;
encrypted = password;
//修改版本
std::string encrypted(password);
2.for循环过程,
在以下方法中定义于循环外,将造成1次构造,1次析构,n次赋值运算。构造于循环内,将构造n次构造,n次析构。主要根据其中的赋值运算消耗进行运算。
Widget w;
for(int i = 0; i<n; ++i){
w = 取决于;
}
二、尽量少做转型动作
1.旧时转型
T(expression)
2.新式转型
(1)const_cast(expression)
C++中唯一的用于转除对象的常量性
(2)dynamic_cast(expression)
安全向下转型,用来决定某对象是否归属集成体系中的某个类型。它是唯一无法用旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作
想在一个derived class对象身上执行derived class操作函数,但是手上只有一个”指向base“的pointer或reference。
(3)reinterpret_cast(expression)
意图执行低级动作,例如将一个pointer to int转型为一个int。
(4)static_cast(expression)
用来强迫隐式转换,例如将non-const对象转为const对象,或将int转为double,也可以执行void*转为typed指针,将pointer-to base转为pointer-to derived
doSomeWork(Widget(15)); //旧式风格
doSomeWork(static_cast<Widget(15)>); //新式风格
int转为double会产生真实的代码
3.单一对象(例如一个类型为Derived的对象)可能拥有一个以上的地址(例如“以base*)指向它”和“以Derived*指向它
class Base{};
class Derived: public Base{};
Derived d;
Base* pb = &d;
4.转型动作有时候会因为理解错误导致代码出问题,如下代码所示,针对this对象的Window部分通过static_cast创建了一个副本,再对这个副本进行了onResize()的调用。
class Window{
public:
virtual void onResize(){...}
};
class SpecialWindow: public Window{
public:
virtual void onResize(){
static_cast<Window>(*this).onResize();
... // SpecialWindow方法
}
}
若想调用Window的onResize()方法作用在自己身上,修改如下:
Window::onResize();
5.使用dynamic_cast实现版本执行速度相当慢,针对一个类所使用时,会对其每一层的派生类进行strcmp调用。要十分注意其效率和速度
错误做法:直接转换,速度慢
class Window{...};
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
VPW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin();
iter != winPtrs.end(); ++iter){
if(SpecialWindow * psw = dynamic_cast<SpecialWindow* >(iteer->get()))
}
(1)应对方法1,使用容器并在其中存储直接指向derived class对象的指针(智能指针)
class Window{...};
typedef std::vector<std::tr1::shared_ptr<SpecialWindow>> VPW;
VPW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin();
iter != winPtrs.end(); ++iter){
(*iter)->blink();
}
(2)应对方法2,通过base class接口处理”所有可能之派生类“,在base class中声明缺省虚函数,在派生类声明具体实现
class Window{
public:
virtual void blink(){ } // 缺省虚函数
};
class SpecialWindow: public Window{
public:
virtual void blink(){
... // 实现
}
};
typedef std::vector<std::tr1::shared_ptr<Window>> VPW;
VPW winPtrs;
...
for(VPW::iterator iter = winPtrs.begin();
iter != winPtrs.end(); ++iter){
(*iter)->blink();
}
三、 避免返回handles(号码牌)指向对象内部成分
1.当我们在const函数中返回了reference,很可能调用者能通过reference更改内部数据。指针和迭代器同样。
解决方法:可以声明该函数返回的类型也是const类型
2.使用handles还有可能导致指针空悬的问题。如下代码所示,在进行完语句1后,boundingBox(*pgo*).upperLeft()的对象将会自动进入析构函数,导致pUpperLeft指向的地址的内容为空
class GUIOject{...};
const Rectangle
boundingBox(const GUIObject& obj);
GUIObject* pgo;
...
const Point* pUpperLeft = & (boundingBox(*pgo).upperLeft()); // 语句1
四、为“异常安全”而努力是值得的
1.使用互斥器的情况,以下方法中安全性很差。
(1)不泄露任何资源:在代码中如果在加锁后解锁前,出现了异常,将发生锁解不开的情况。
(2)不允许数据败坏:以上代码中如果new Image失败了,此时imageChanges++已经运行包括delete,但是其实新图像并没有被成功初始化
class PrettyMenu{
public:
void changeBackground(std::istream& imgSrc);
private:
Mutex mutex;
Image* bgImage;
int imageChanges;
};
void PrettyMenu::changeBackground(std::istream& imgSrc){
lock(&mutex);
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
unlock(&mutex);
}
2.异常安全有三种保证
(1)基本承诺:如果异常被抛出,程序内任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态。但是现实状态恐怕不可预料,因为有可能我们一旦抛出异常,PrettyMenu对象可以基于拥有原背景图像或者是一个初始化图像,但是客户无法预期是哪一种情况。
(2)强烈保证:如果异常被抛出,程序状态不改变。如果没被抛出就是完全成功。
(3)不抛掷保证:承诺绝不抛出异常,保证函数一定是正确的。
对以上代码的修改方法一,在以下代码中,tr1::shared_ptr::reset函数只有在新图像被成功创建后才会进行调用,在调用时才会自动在其内部运行原对象的delete函数。同时该方法也让代码进一步缩短,提高了安全性:
class PrettyMenu{
std::tr1::shared_ptr<Image> bgImage;
};
void PrettyMenu::changeBackground(std::istream& imgSrc){
Lock ml(&mutex);
bgImage.reset(new Image(imgSrc)); // 通过“new Image”的执行结果设定bgImage内部指针
++imageChanges;
}
修改方法二,加入copy and swap方法,更容易保证强烈的安全性,但也会带来相应的时间和空间消耗。先给预修改的对象构建一个副本,针对副本进行修改,修改完成后再通过swap进行替换回去,哪怕这中间发生异常错误,原对象依然保持不变。
struct PMImpl{
std::shared_ptr<Image> bgImage;
int imageChanges;
}
class PrettyMenu{
std::tr1::shared_ptr<PMImpl> pImpl;
};
void PrettyMenu::changeBackground(std::istream& imgSrc){
using std::swap;
Lock ml(&mutex);
std::tr1::shared_ptr<Image> pNew(new PMImpl(*pImpl))
pNew->bgImage.reset(new Image(imgSrc));
++pNew->imagechages;
swap(pNew, pImpl);
}
以上问题中,即使使用copy and swap方法不能总是带来强烈保证,在以下方法中,可能f1方法是强烈保证,但是f2发生了异常,而此时f1修改的内容因为已经修改我们需要事先记录才能回到原状态。
void someFunc(){
... // 创建副本
f1();
f2();
... // 将修改后的状态置换过来
}
五、透彻了解inlining的里里外外
1.inline函数会让“对此函数的每一个调用”都以函数本体替换它。因此可能会增加目标码的大小,但是如果是普通函数也会占用,不过占用不多,因此如果inline函数本体很小,编译器针对“函数本体”所产出的码可能比针对“函数调用”产出的码更小,因此才导致较小的目标码和较高的指令缓存装置集中率。
2.inline函数有隐喻方式和明确。
隐喻方式,通常为成员函数:
class Person{
public:
int age() const{ return theAge; } // 隐喻方式
private:
int theAge;
}
明确方式,直接在函数前加inline关键字,比如max函数
3.inline通常不用用于针对virtual函数。inline是执行前,先将调用动作替换为被调用函数的本体,virtual是等待,直到运行期才确定调用哪个函数。
inline函数是否真是inline取决于建置环境,取决于编译器。大多数编译器在无法将要求的函数inline化时,会给一个警告信息。
4.有时候虽然编译器意愿inlining某个函数,还是可能为该函数生成一个函数本体。比如,如果程序要取某个inline函数的地址,编译器通常必须为此函数生成一个outlined函数本体,因为函数指针必须指向一个存在的函数,inline函数没有实际地址,直接做的替换操作。
编译器通常不对“通过函数指针而进行的调用”实施inlining。
inline void f(){...}
void (*pf) () = f;
...
f(); // 正常inline
pf(); // 不被inline
5.构造函数和析构函数不适合inlining,因为如果其有派生类或者继承类,其构造函数以及析构函数通常是一层嵌套一层的,如果声明为inline的话,会导致空间占用等等
6.对于Template应该多多考虑是否inline,因为Template有多种形式,这每种形式是否都能inline。inline通常发生在编译期,Template通常在编译器,有时也在连接期,在连接期的话将会导致异常。