版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/budding0828/article/details/86624952
第五章:实现
条款26:尽可能延后变量定义式的出现时间
string encryptPassword(const string& password)
{
...
string encrypted; //调用默认构造函数default-construct
encrypted = password; //赋值
...
}
//更优版本
string encryptPassword(const string& password)
{
...
string encrypted(password); //调用copy构造函数
...
}
条款27:尽量少做转型动作
- C风格转型动作:
(T)expression //将expression转型为T
T(expression) //将expression转型为T
两种形式是等价的。
- C++提供四种新式转型
const_cast<T> (expression) //常量性转除
dynamic_cast<T> (expression) //安全向下转型(继承)
reinterpret_cast<T> (expression) //执行低级转型,实际行为取决于编译器(少见)
static_cast<T> (expression) //迫使隐式转换。将non-const转为const; 或者将int转为double等等
- dynamic_cast (expression)实现的执行速度非常慢
应尽量避免在代码中使用dynamic_cast。之所以需要dynamic_cast,通常是因为你想在一个你认定为derived class对象身上执行derived class操作函数,但是你的手上只有一个“指向base”的pointer或reference。
解决办法是:使用virtual
- 宁可使用C++style(新式)转型,不要使用旧式转型
条款28:避免返回handles指向对象内部成分
如果const成员函数传出一个reference,后者所指数据与对象自身有关联,那么函数调用者便可以修改内部数据。
class Rectangle{
public:
...
Point& upperLeft() const {return pData->ulhc;}
...
}
Rectangle rec;
Point change = rec.upperLeft(); //此时change可以改变rec内部数据
解决方法:在返回类型上加上const
const Point& upperLeft() const {return pData->ulhc;}
条款29:为“异常安全”而努力是值得的
lock(&mutex);
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
unlock(&mutex);
当异常被抛出时,带有异常安全性的函数会
- 不泄露任何资源:一旦new Image(imgSrc)异常,对unlock的调用就绝对不会执行,于是互斥器就永远地被把持住了。
解决办法:使用对象管理资源
Lock ml(&mutex); //使用对象管理资源
delete bgImage;
++imageChanges;
bgImage = new Image(imgSrc);
- 不允许数据败坏:如果new Image(imgSrc)异常,bgImage就是指向一个已被删除的对象,imageChanges也已被累加,但其实没有新的图像被成功安装。
解决办法:使用std::tr1::shared_ptr中的reset函数
class P{
...
std::tr1::shared_ptr<Image> bgImage;
...
};
void P::changeBackground(istream& imgSrc)
{
Lock ml (&mutex);
bgImage.reset(new Image(imgSrc));
++imageChanges;
...
}
shared_ptr::reset函数只有在其参数(也就是new Image(imgSrc)的执行结果)被成功生成之后才会被调用。delete只在reset函数内被调用,所以就解决了,新资源没有,而旧的已经被delete的问题。
copy and swap:为你打算修改的对象作出一份副本(copy),然后在那副本身上做一切必要的修改。若有任何修改动作抛出异常,原对象仍保持未改变状态。待所有改变都成功后,再将修改过的那个副本和原对象在一个不抛出异常的操作中置换(swap)
条款30:透彻了解inlining的里里外外
-
inline的坏处:
- inline会增加目标代码的大小,会导致额外的换页行为,降低指令高速缓存装置的击中率。
- 一旦程序库设计者决定改变f(inline),所有用到f的客户端程序都必须重新编译。如果f是non-inline,那么只需要客户端重新连接就好
-
inline只是对编译器的一个申请,不是强制命令。大部分编译器拒绝将太过复杂(例如带有循环或者递归)的函数inlining。而所有对virtual函数的调用也都不会使用inline。
-
一个表面看上去inline的函数是否真的inline,取决于你的环境,主要取决于编译器。如果无法inline,编译器会给出一个警告。
条款31:将文件间的编译依存关系降至最低
- C++并没有将接口从实现中分离做得很好
#include <string> //include任何一个改变,都需要重新编译Person class文件
#include "date.h"
class Person
{
public:
string name;
Date theBirthDate;
...
}
- 分离办法一:把Person分割为两个classes,一个只提供接口,另一个负责实现该接口。接口与实现分离
核心思想:把Person变成Handle class。一个handle class 并不会改变它做的事,只会改变它做事的方式。他们会把所有函数转交给相应的实现类,并由后者完成实际工作。
#include <string>
class PersonImpl; //Person 实现类的前置声明
class Date; //Person 接口用到的class的前置声明
class Person
{
public:
Person(const string& name, const Date& birthday);
string name() const;
string birthDate() const;
private:
std::tr1::shared_ptr<PersonImpl> pImpl; //指针,指向实现物
}
成员函数的实现
#include "Person.h"
#include "PersonImpl.h" //PersonImpl有着和Person完全相同的成员函数,两者接口完全相同
Person::Person(const string& name, const Date& birthday):pImpl(new PersonImpl(name, birthday)){}
string Person::name() const
{
return pImpl->name();
}
- 分离方法二:将Person变成抽象基类,成为interface class
核心思想:interface class不带成员变量,也没有构造函数,只有一个virtual析构函数以及一组pure virtual函数,用来叙述整个接口。
class Person
{
public:
virtual ~Person();
virtual string name() const = 0;
virtual string birthDate() const = 0;
}
class RealPerson: public Person{
public:
RealPerson(const string name, const Date& birthday):theName(name),theBirthday(birthday){}
string name() const;
private:
string theName;
Date theBirthday;
}
- 小结:handle class 和 interface class 解除了接口和实现之间的耦合关系,从而降低文件间的编译依存性。