不只应该延后变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义直到能够给它初值实参为止。这样不仅能够避免构造(和析构)非必要对象,还可以避免无意义的default构造行为。
尽可能延后变量定义式的出现时间
//不理想编码
std::string encryptPassword(const std::string& password)//encrypt加密
{
using namespace std;
string encrypted;
if(...){throw logic_error("wrong")};//如果if抛出异常,则encrypted未被使用
return encrypted;
}
//理想编码
std::string encryptPassword(const std::string& password)//encrypt加密
{
using namespace std;
if(...){throw logic_error("wrong")};
string encrypted;
return encrypted;
}
(default构造函数构造出一个对象然后对它赋值)比(直接在构造时指定初值)的效率差
//不理想编码
std::string encrypted;
encrypted = password;
//理想编码
std::string encrypted(password);
涉及到循环的变量:
方法A:
widget w;
for(int i = 0;i<n ;i++){
w = i;
...
}
//1个构造函数+1个析构函数+n个赋值操作
方法B:
for(int i = 0;i<n ;i++){
widget w(i);
...
}
//n个构造函数+n个析构函数
A和B比较:
- A造成名称w的作用域比方法B更大,有时对程序的可理解性和易维护性造成冲突。
- 如果赋值成本比“构造+析构”成本低,或者正在处理代码中效率高度敏感的部分则使用A,否则使用B
尽量少做转型动作(minimize casting)-优良的C++代码很少使用转型
三种不同的转型形式:
C风格的转型
(T)expression
函数风格的转型
T(expression)
-
- const_cast< T >(expression)
- dynamic_cast< T >(expression)
- static_cast< T >(expression)
reinterpret_cast< T >(expression)
尽量避免转型,特别是注重效率的代码中避免dynamic_cast。dynamic_cast的许多实现版本执行速度相当慢
之所以需要dynamic_cast,通常是因为你想在一个你认定派生对象上执行派生类操作函数,但却只有一个指向基类的指针或引用
绝对必须避免连串的dynamic_cast,例如:
class window{}; class specialwindow1:public window{}; ...//其它派生类的定义 typedef std::vector<std::tr1::shared_ptr<window> > VPW; //> >必须中间用空格分开,不然会被当做右移操作符 VPW winPtrs; ... for(VPW::iterator iter = winPtrs.begin() ; iter!= winPtrs.end() ; ++iter) { if(specialwindow1 * psw1 = dynamic_cast<specialwindow1>(iter->get())){} elseif(specialwindow1 * psw1 = dynamic_cast<specialwindow1>(iter->get())){} ... } //这段代码的问题在于:1、如果window类继承体系改变,则所有这类代码需要修改,或者加入新的派生类是或许需要加入条件分支。 //这样的代码应该以某些“基于virtual函数调用”的东西取而代之。
- 如果有个设计需要转型动作,试着发展无需转型的代替设计
//需要转型动作的设计 typrdef std::vector<std::tr1::shared_ptr<window> > VPW; VPW::winPtrs; for(VPW::iterator iter = winPtrs.begin() ; iter!= winPtrs.end() ;++iter) { if(specialwindow1 *psw = dynamic_cast<specialwindow1>(iter->get())){psw->fun();} }
可以改为
//无需转型的代替设计 typrdef std::vector<std::tr1::shared_ptr<specialwindow1> > VPSW; VSPW::winPtrs; for(VPW::iterator iter = winPtrs.begin() ; iter!= winPtrs.end() ;++iter) { psw->fun(); } //但是这种做法使得该容器只能存储一种window的派生类specialwindow1
如果转型是必要的,试着将它隐藏于某个函数背后。
尽量使用C++风格的转型,不要使用1和2旧式转型,C++转型容易识别且有分门别类的执掌,出错时易于查找
似是而非的转型编码
单一对象可能拥有一个以上的地址,例如“以base* 指向它”时的地址和“以derived*指向它”时的地址。一旦使用多重继承,这事几乎一直发生。
class window{ public: virtual void onresize(){} ... }; class specialwindow:public window{ public: virtual void onresize(){ static_cast<window>(*this).onresize();//我们想让派生类的虚函数onresize先调用基类的对应函数,但此处编码不可行 } };
错误分析:此时将this指针转型为window基类,并调用的是window::onresize。但是,调用的是稍早于转型动作所建立的一个this指针的base class成分的副本的onresize,这时会导致其base class成分的更改没有落实,但derived class成分的更改落实了。
正确示例
class specialwindow:public window{ public: virtual void onresize(){ window::onresize(); } };
避免返回handles(号码牌,用来取得某个对象,有:引用、指针、迭代器)指向对象内部成分
返回一个代表对象内部数据(内部数据=成员变量+被声明为protected、private的函数)的handle,
- “降低对象封装性”的风险
- 也可能导致“虽然调用const成员函数却造成对象状态被更改”。
- 会面临“handle比其所指对象更长寿”的风险,handle比其所指对象更长寿会导致虚吊号码牌(dangling handle)。
异常安全性(exception safety)
当异常被抛出时,带有异常安全性的函数会:
- 不泄漏任何资源
- 不允许数据败坏
异常安全函数提供以下三个保证之一:(对大部分函数而言,抉择往往落在基本保证和强烈保证之间)
基本承诺:如果异常被抛出,程序内的任何事 物仍然保持在任何一个有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态(例如所有的class约束条件都继续获得满足)。
强烈保证:如果异常被抛出,程序状态不改变。也就是如果函数成功,就是完全成功,如果函数失败,程序会恢复到调用函数之前的状态。
“copy and swap “策略会导致强烈保证。该策略的原则:为打算修改的对象(原件)做一个副本,在副本身上做一切必要的修改,若有任何修改动作抛出异常,原件仍然保持未改变状态。待所有改变都成功后,再将修改过的副本和原件在一个不抛出异常的操作中置换(swap)。
不抛掷(nothrow)保证:承诺绝不抛出异常,因为它们总是能够完成原先承诺的功能。作用于内置类型身上的所有操作都提供该保证。
函数的声明式并不能告诉你函数是否是正确的、可移植的或高效的,也不能够告诉你是否提供任何异常安全性保证。所有这些性质都由函数的实现决定,无关乎声明。
函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者
inline函数– 将对此函数的每个调用都以函数本体替换之
inline只是对编译器的一个申请,不是强制命令。这项申请可以隐喻提出,也可以明确提出
隐喻方式:将函数定义与class定义式内:
class Person{ public: ... int age() const{return theAge;}//隐喻的inline申请 private: int theAge; };
这样的函数通常是成员函数,friend函数也可被定义与class内。
明确声明inline函数是在其定义式前加上关键字inline
template<typename T> inline const T& std::max(const T& a, const T& b){return a<b ? b:a;}//明确声明 //inline函数和template两者通常被定义于头文件。但是不要只因为function template出现在头文件,就将其声明为inline。
编译器通常不对“通过函数指针而进行的调用”实施inline,这意味对inline函数的调用有可能被inline,也有可能不被inline,取决于该调用的实施方法:
inline void f() {...}
void (* pf)() = f;
...
f();//这个调用将被inlined,因为是一个正常调用。
pf();//这个调用或许不被inlined,因为通过函数指针达成的。
因此,一个表面上看似inline的函数是否真是inline,取决于编译器。如果编译器无法将你要求的函数inline化,会给你一个警告信息。
大部分调试器面对inline函数都束手无策,也就是无法设立断点。因此,慎重使用inline,将大多数inline限制在小型、被频繁调用的函数身上。
将文件之间的编译依存关系降至最低
举例:
class person{
public:
person(const std::string& name , const Date& birthday , const Address& addr);
std::string getname() const;
std::string getbirthDate() const;
std::string getaddress() const;
private:
std::string thename;
Date thebirthdate;
Address theaddress;
}
该文件还应该有:
#include<string>
#include"date.h"
#include"address.h"
person类的定义文件和含入文件之间形成了一种编译依存关系。如果这些头文件中有任何一个被改变,或这些头文件所依赖的其它头文件有任何改变,那么每一个含入person定义文件的文件都重新编译,任何使用person的文件也必须重新编译。