Effective C++ 第五章:实现

版权声明:本文为博主原创文章,未经博主允许不得转载。 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 解除了接口和实现之间的耦合关系,从而降低文件间的编译依存性。

猜你喜欢

转载自blog.csdn.net/budding0828/article/details/86624952
今日推荐