第五章:实现
条款26:尽可能延后变量定义式的出现时间(Postpone variable definitions as long as possible)
声明一个变量其带有构造函数和析构函数,我们必须小心这点
std::string encryptPassword(const std::string password){
using namespace std;
string encrypted;
if(password.length()< MinimumPasswordLength){
throw logic_error("Password is too short");
}
...
return encrypted;
}
如果有异常抛出string encrypted就没有被使用到,同时我们付出了构造和析构成本,我们延后其定义:
std::string encryptPassword(const std::string password){
using namespace std;
//string encrypted;
if(password.length()< MinimumPasswordLength){
throw logic_error("Password is too short");
}
string encrypted;
...
return encrypted;
}
string encrypted调用default构造函数(可能会毫无意义),通过copy构造函数定义并初始化,(一个加密函数encrypt)
void encrypt(std::string& s);
std::string encryptPassword(const std::string password){
... //检查长度--同上
std::string encrypted(password); //copy构造函数
encrypt(encrypted);
return encrypted;
}
当出现循环时:
//方法A:定义于循环外
Widget w;
for(int i=0;i<n;i++){
w = 取决于i值;
...
}
//方法B:定义于循环内
for(int i=0;i<n;i++){
Widget w(取决于i值)
...
}
方法A:1个构造函数+1个析构函数+n个赋值操作
方法B:n个构造函数+n个析构函数
具体方法取决于赋值的成本和构造+析构的成本。
请记住:
尽可能延后变量定义式的出现。这样做可增加程序的清晰度并改善程序效率。
条款27:尽量少做转型动作(Minimize casting)
首先是常见的转型风格:
(T)expression;
T(expression);
c++提供四种新式转型:
const_cast<T>(expression)
dynamic_cast<T>(expression);
reinterpret_cast<T>(expression);
static_cast<T>(expression);
const_cast:将常量性转除;
dynamic_cast:执行“安全向下转型”;
reinterpret_cast:执行低级转型例:将一个pointer to int 转型为一个int;
static_cast:强迫隐式转换例:将non-const转为const;将int转为double等;
请记住:
如果可以,尽量避免转型,特别在注重效率的代码中避免dynamic_cast
如果转型是必要,试着将它隐藏于某个函数背后。客户随后可以调用函数,而不需将转型放进他们自己代码内
宁可使用c+±style转型,不要使用旧时转型
条款28:避免返回handles指向对象内部成分(Avoid returning “handle” to object internals)
class Point{
public:
Point(int x,int y);
...
void setX(int newVal);
void setY(int newVal);
};
strcut RectData{
Point ulhc;
Point lrhc;
};
class Rectangle{
public:
Point& upperLeft() const {return pData->ulhc;}
Point& lowerRight() const {return pData->lrhc;}
...
private:
std::trl::shared_ptr<RectData> pData;
};
虽然可以通过编译,然而这是错误的,因为返回的reference指向private内部数据,调用者可以通过reference修改数据!:
Point coord1(0,0);
Point coord2(100,100);
const Rectangle rec(coord1,coord2); //rec是个const矩形从(0,0)到(100,100)
rec.upperLeft().setX(50); //现在rec变成了从(50,0)到(100,100)
我们只需在返回类型加上const即可:
class Rectangle{
public:
const Point& upperLeft() const {return pData->ulhc;}
const Point& lowerRight() const {return pData->lrhc;}
...
};
请记住:
避免返回handles(包括reference、指针、迭代器)指向对象内部。可增加封装性,帮助const成员函数的行为像个const,并将发生“虚吊号码牌”的可能性降至最低
条款29:为"异常安全"而努力是值得的(Strive for exception-safe code)
当异常被抛出时带有异常安全性的函数会:不泄露任何资源、不允许数据败坏
异常安全函数提供三种保证:
1.基本承诺
2.强烈保证
3.不抛掷保证
请记住:
-异常安全函数即使发生异常也不会泄漏资源或允许任何数据结构败坏这样的函数区分为三种可能保证:基本型、强烈型、不抛出异常型
-"强烈保证"往往能够以copy-and-swap实现出来,但"强烈保证"并非对所有函数都可实现或具备现实意义
-函数提供"异常安全保证"通常最高只等于其所调用之各个函数的"异常安全保证"中最弱者
条款30:透彻了解inlining的里里外外(Understand the ins and outs of inlining)
首先我们先看看inline到底是个啥,简单点说就是"将对此函数的每一个调用"都以函数本体替换之,这会增加目标码大小,造成程序体积太大。记住,inline只是对编译器的一个申请,而不是强制命令!!
一个隐喻方式是将函数定义于class定义式内:
class Person{
public:
int age() const{return theAge;} //隐喻的inline申请
...
private:
int theAge;
};
inline函数通常一定被置于头文件内,大多数建置环境在编译过程中进行inline,所以在将"函数调用"替换为"函数本体"前,编译器必须知道函数长啥样。templates通常也被置于头文件内,编译器为将它具现化,需要知道它长什么样子。
同时对inline函数的调用是否被inline还与该调用的实施方式有关:
inline void f(){...} //假设编译器inline“对f的调用”
void (*pf) () = f; //pf指向f
...
f(); //这个调用将被inline,正常调用
pf(); //调用或许不被inline,因为它通过函数指针
请记住:
-将大多数inlining限制在小型、被频繁调用的函数身上。可是日后调试过程和二进制升级更容易,也可使潜在的代码膨胀问题最小化,使程序的速度提升机会最大化
-不要因为function template出现在头文件。就将它们声明为inline