条款26 尽可能延后变量定义式的出现时间
总结:
尽可能延后变量定义式的出现,这样做可增加程序的清晰度并改善程序效率。
1、 错误的方式:
string func(const string& password){
string temp;
if(password.length()<Min){
throw logic_error("Password is too short");
}
...
return temp;
}
如果丢出了异常,string temp的构造和析构成本都浪费了。
所以,应该把string temp放到throw后面。
2、更进一步
不应该只是延后变量的定义,而是应该尝试延后这份定义直到能够给他初值实参为止。
通过“”default构造函数构造出一个对象然后对他赋值“”,比,“”直接在构造时指定初值效率差“”。
string func(const string& password){
if(password.length()<Min){
throw logic_error("Password is too short");
}
// 直接给它初值实参
string temp(password);
return temp;
}
条款27 尽量少做转型动作
总结
1、如果可以,尽量避免转型,特别是在注重效率的代码避免dynamic_casts。
2、如果转型是必要的,试着将它隐藏于某个函数背后,客户随时可以调动该函数,而不需将转型放进他们自己的代码内。
3、宁可使用C++新式转型,不要使用旧式转型。
1、四种新式转型
const_cast<T>(expression) 将对象的常量性const 移除,将const转为non-const
dynamic_cast<T>(expression) // 安全向下转型,决定某对象是否归属继承体系中的某个类型
reinterpret_cast<T>(expression) // 执行低级转型
static_cast<T>(expression) // 强迫隐式转换,将non-const转为const
2、
class Base {};
class Derived:public Base {};
Derived d; // 派生类对象
Base* pb=&d; // 基类指针指向派生类对象
基类指针指向派生类对象,隐喻的将Derived* 转换为 Base*。
单一对象可能拥有一个以上的地址(Base*指向它的地址 和 Derived*指向它的地址)
3、拿掉转型动作
class Derived:public Base{
public:
virtual void func(){
Base::func();// 直接调用Base类的成员函数
}
}
4、dynamic_cast
之所以需要dynamic_cast,因为你想在一个你认定为derived class对象身上执行derived class 操作函数,但你的手上却只有一个“指向base”的pointer活reference ,你只能靠他们来处理对象。
安全向下转型,决定某对象是否归属继承体系中的某个类型。
两种方式:
1、使用容器直接指向derived class 对象的指针,如此便消除了 通过base class 接口处理对象。
typedef vector<shared_ptr<Derived>> VPSW;// 直接用派生类
VPSW bin;// 派生类vector的对象
for(VPSW::iterator iter=bin.begin();iter!=bin.end();++iter;){
(*iter)->blink();
}
2、base class内提供virtual函数做你想对各个派生类想做的事。
条款28 避免返回handles指向对象内部成分
避免返回handles(包括引用、指针、迭代器)指向对象内部。遵守这个条款可增加封装性。
错误的行为:
class Retangle{
public:
Point& upperLeft() const {return pData->ulhc;}// 成员函数返回引用
}
const Retangle rec();// const类型对象
rec.upperLeft().setX(50); //const对象,但是成员函数返回引用即可以更改,bitwise constness
const对象,但是成员函数返回引用即可以更改,bitwise constness问题
所以:应该避免返回引用指向对象内部。
解决方式:让成员函数返回值const
const Point& upperLeft() const {return pData->ulhc;}// 成员函数返回const引用
条款29 为“异常安全”而努力是值得的
条款30 透彻了解inling的里里外外
总结:
1、inlining在小型、被频繁调用的函数身上,提升程序速度。
2、不要只因为function templates出现在头文件,就将他们声明为inline。
(1)为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。在系统下,栈空间是有限的,假如频繁大量的使用就会造成因栈空间不足而导致程序出错的问题
(2)建议:inline函数的定义放在头文件中,仅仅声明是没用的。inline 是一种“用于实现的关键字”,而不是一种“用于声明的关键字”。
// 头文件
class A
{
public:
inline void Foo(int x, int y){
...
};
}
一、inline只是对编译器的一个申请,不是强制命令
(1)隐喻提出(隐喻方式是将函数定义于class定义式内):
class Person {
public:
...
int age() const {return theAge;}//一个隐喻的inline申请
...
private:
int theAge;
};
(2)明确提出(明确申请inline函数的做法是在其定义式前加上关键字inline):
template<typename T>
inline const T& std::max(const T& a, const T& b) {
return a < b? b: a;
}
二、virtual函数+函数指针+构造析构函数 不能inline
大部分编译器拒绝将太过复杂(例如带有循环或递归)的函数inlining,而所有对virtual函数的调用也都会使inlining落空。因为virtual意味着“等待,直到运行期才确定调用哪个函数”,而inline意味“执行前,先将调用动作替换为被调用函数的本体”。如果编译器不知道该调用哪个函数,那肯定就没法inlining了!
编译器通常不对“通过函数指针而进行的调用”实施inlining。
inline void f(){…} //假设编译器有意愿inline“对f的调用”
void (*pf)() = f;
f();//这个调用将被inlined,因为是一个正常调用
pf();//这个调用或许不被inlined,因为通过指针达成
条款31 将文件间的编译依存关系降到最低