PS,1880后程序员

看不完的牙,写不完的程序,跑不完的步。
随笔 - 97, 文章 - 34, 评论 - 10, 引用 - 0
数据加载中……

Effecitive C++读书笔记 第五章 实现 Implementations

 

Item26Postpone variable definitions as long as possible.尽可能延后变量定义的出现时间

这里所说的变量是variable of type,它需要调用constructordestructor

简单情况下,如果函数抛出异常,导致变量没有被使用到,但是由于已经定义过,其实还是需要调用它的constructordestructor

std::string encryptPassword(const std::string& password)

{

std::string encrypted; //这是错误的写法,这个定义已经需要调用构造函数了。

encrypted = password;

encrypt(encrypted);

return encrypted;

}

正确的写法:

std::string encryptPassword(const std::string& password)

{

std::string encrypted(password); //正确的写法。

encrypt(encrypted);

return encrypted;

}

postpone有两层含义:

1 postpone a variable’s definition until right before you have to use the variable

2 postpone the definition until you have initialization arguments for it.

循环怎么办?

最常见的写法一种是在循环体里面定义变量,另一种是在循环体执行前定义一个变量。

A

B

approach

Widget w;

for(int i=0; i<n;++i)

{

w=取决于i的某个值;

}

for(int i=0; i<n;++i)

{

Widget w(取决于i的某个值);

}

开销

1 constructor + 1 destructor + n assignment

if assignment小于 constructor-destructor对,这种写法比较有效

n constructor + n destructor

defect

这种方法定义的变量在较大的范围都是可见的,不好。

结论:除非你明确知道approach A的开销小于approach B,否则尽量使用approach B

Item 27Minimize casting尽量少做转型动作

C++提供的4中cast形式

const_cast<T>(expression)

dynamic_cast<T>(expression)

reinterpret_cast<T>(expression)

static_cast<T>(expression)

尽量使用这些新风格的转型,最起码的它好认啊。

cast,到底做了什么?

基类和派生类的指针有时是不相等的,存在一个偏移量offsetsingle object might have more than one address。为啥说有时呢,因为这取决于Compiler。所以不能想当然在基类指针上加个值就得到什么。

dynamic_cast

cast并不是啥都没有做的,它会创建一个临时的基类的copy,然后调用的是这个临时类的方法,和当前被cast类无关哦。

同时多级的继承关系里面cast也要通过字符串比较来确定匹配的基类,因此如果对性能有要求,就尽量少使用cast

解决方法

有两种:

1 使用type-safe的容器

这种方法的缺陷是容器里面只能允许有1种类型的指针

ExampleP121

2 在基类里面定义虚函数

class Window{

public:

virtual void blink();

};

class SpecialWindow : public Window {

public:

virtual void blink();

};

typedef std::vector<std::tr1::shared_ptr<Window>> VPW;

VPW winPtrs;

for(VPW::iterator iter=winPtrs.begin(); iter!=winPtrs.end(); ++iter)

(*iter).blink(); //注意这里就不需要dynamic_cast

Item 28Avoid returning "handles" to object internals.避免返回handles指向对象内部成分 123

handles

handles可以理解为句柄,或者说是一个“把柄”,握着这个“把柄”,你可以修改对象的数据成员。

handle包括referencepointeriterator

问题出现的背景

很简单,就是看上去函数返回的是一个const对象,但是由于是handle,导致数据还是可以被client修改的。这种情况,debug时很难发现,而且给人的感觉是系统不稳定,后果很严重,不可轻视。

struct rectData {

Point ulhc;

Point lrhc;

};

class Rectangle {

public:

Point& upperLeft() const {return pData->ulhc; }

private:

std::tr1::shared_ptr<RecData> pData;

};

const member function:不能修改数据成员。

client调用:

const Rectangle rec(Point (0,0), Point(100,100));

rec.upperLeft().setX(50);//坐标就这么不知不觉中被修改了

结论

避免返回指向对象内部的handles,这种handles包括:pointerreferenceiterator

如果某个函数的访问级别低,禁止其它成员函数返回指向这个函数的指针。因为如果这样,就可以通过函数指针来调用这个函数了。

Item 29Strive for exception-safe code.为“异常安全”而努力是值得的 127

class PrettyMenu{

public:

void PrettyMenu::changeBackgroud(std::istream& imgSrc) ;

private:

Mutex mutex;

int imageChanges;

Image * bgImage;

};

void PrettyMenu::changeBackgroud(std::istream& imgSrc)

{

Lock m1(&mutex); //Item13,对象管理资源的典型应用

delete bgImage;

++imageChanges;

bgImage = new Image(imgSrc);

}

异常安全性函数应该提供以下3种保证之一:

基本保证

如果异常抛出,程序里所有的东西都保持有效的状态。

增强保证

如果异常抛出,程序的状态保持不变。

不抛出异常保证

决不抛出异常。因为总能够完成希望要做的事情。

这同时也是一种理想状态。

一般情况下,我们的选择是在basicstrong异常保证之间。

重新写changeBackgroud,提供basic异常保证:

class PrettyMenu{

std::tr1::shared_ptr<Image> bgImage;

};

void PrettyMenu::changeBackgroud(std::istream& imgSrc)

{

Lock m1(&mutex); //Item13,对象管理资源的典型应用

bgImage.reset(new Image(imgSrc) );

++imageChanges;

}

strong异常保证的设计策略说出来也很简单,就是copy and swap。具体描述见P131 L5开始的描述。

strong异常保证

基本思路就是copy and swap

struct PMImpl {

std::tr1::shared_prt<Image> bgImage;

int imageChanges;

};

class PrettyMenu{

private:

Mutex mutex;

std::tr1::shared_ptr<PMImpl> pImpl;

};

void PrettyMenu::changeBackgroud(std::istream& imgSrc)

{

using std::swap;

Lock m1(&mutex); //Item13,对象管理资源的典型应用

std::tr1:: shared_prt< PMImpl > pNew(new PMImpl(*pImpl));

pNew->bgImage.reset(new Image(imgSrc)); //修改副本

++pNew-> imageChanges;

swap(pImpl, pNew); //置换

}

trade-off折中考虑异常保证

并不是一定要提供strong异常保证就是好的,这里是需要一个trade-offstrong异常保证的代价一定是很大的,efficiencycomplexity都是要考虑的因素。

同时如果函数中调用了没有异常保证的其它函数,这个函数也不能是异常保证的。

void someFunc()

{

f1();

f2();

}

这引申出一个结论:如果代码中调用了没有异常保证的C的传统代码,这样的函数,就大可不必还要提供strong的异常保证了

Item 30Understand the ins and outs of inlining.透彻了解inlining的里里外外 134

什么是内联函数inline

将函数指定为 inline 函数,(通常)就是将它在程序中每个调用点上“内联地”展开。函数本体Function body是不存在的。

inline是对Compiler的请求,而不是对命令的。

The inline specification is only a request to the compiler. The compiler may choose to ignore this request.

一般来说,内联机制适用于优化小的、只有几行的而且经常被调用的函数。

大多数的编译器都不支持递归函数的内联。

复杂函数也是不可以的,一个 1200 行的函数也不太可能在调用点内联展开。

虚函数也是不可以的。

inline与函数模板Function Template

template<typename T>

inline const T& std::max(const T& a, const T& b)

{ return a<b?b:a; }

inline函数大多数都是定义在头文件了,这是因为编译器必须知道inline函数是什么样子。

Template也是定义在头文件里,因为Compiler需要知道Template是什么样子,为了初始化Template

Template独立于inline。这是不同的概念,当然函数模板也可以声明成inline

函数指针和内联函数

函数指针指向的内联函数一定不会被inlineCompiler会生成这个函数的本体function body,函数指针就是指向的这个函数本体。

inline void f() {…}

void (*pf)() = f;

f();

pf();//这个调用就不会是inline,因为这是一个函数指针。

构造函数、析构函数和内联函数

构造函数和析构函数不要做内联。

简单的说,如果base constructor内联,派生类中从基类继承来的data member被初始化2次,一次是来自内联,一次来自C++执行基类的构造函数。

同理,析构函数,也就会被释放2L惨了,但是实际上,只有一个对象啊。不死等什么。

内联函数修改的影响

内联函数的机制决定调用内联函数,就是把代码插入调用的位置,因此如果内联函数修改了,所有调用该函数的地方都需要重新编译。如果这个函数不是内联的,修改后,只要重新链接就可以了。

记住

inline仅仅适用于小型的,被频繁调用的函数。

不要因为函数模板定义在头文件里就把它声明成inclineFunction Templateinline这是2个不同的概念。

Item 31Minimize compilation dependencies between files.将文件间的编译依存关系降至最低 140

前向声明forward declaration

1.            首先前向声明仅仅是声明了一个类

class Date; //forward declaration

class Address;

2.            其次前向声明是一种不完全类型

只能以有限的方式使用。只能用于定义指向该类型的指针,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。不能定义该类型的对象。

pimplpointer to implementation

#include <string>

#include <memory>

class PersonImpl;

class date;

class Address;

class Person {

public:

Person(const std::string& name, const date& birthday, const Address& addr);

std::string name() const;

std::string birthday() const;

std::string address() const;

private:

std::tr1::shared_ptr<PersonImpl> pImpl; //point to implementation

};

基本解决之道

replacement of dependencies on definitions with dependencies on declarations.以声明的依赖性替换定义的依赖性

尽量让头文件做到自包含,万一做不到,则让它和其它文件内的声明相互依赖,而不是定义。

规则

如果使用对象引用活对象指针可以解决问题,就避免使用对象,

取代类定义,依赖于类声明

头文件要成对出现,一个是声明,一个是定义。

两种具体的操作方法

Handle Classes

定义Handle class

class Person {

public:

Person(const std::string& name, const Date& birthday, const Address& addr);

std::string name() const=0;

std::string birthday() const=0;

std::string address() const=0;

private:

std::tr1:shared_ptr<PersonImpl> pImpl;

};

std::string Person::name() const

{

return pImpl->name();

}

定义实现类PersonImpl

Handle Classes

这里给出的Handle Classes的定义太精辟了。喜欢J

Classes like Person that employ the pimpl idiom are often called Handle classes.

Making Person a Handle class doesn’t change what Person does, it just changes the way it does it.

Interface Classes

定义接口

class Person {

public:

virtual ~Person();

virtual std::string name() const=0;

virtual std::string birthday() const=0;

virtual std::string address() const=0;

static std::tr1::shared_ptr<Person> Person::create(const std::string& name, const Date& birthday, const Address& addr );

};

定义实现

class RealPerson:public Person {

public:

realPerson(const std::string& name, std::string& birthday, const Address& addr)

:theName(name),theBirthdate(birthday), theAddress(addr)

{}

virtual ~RealPerson()

std::string name() const;

std::string birthday () const;

std::string address () const;

private:

std::string theName;

date theBirdate;

Address theAddress;

};

 

std::tr1::shared_ptr<Person> Person::create(const std::string& name, const Date& birthday, const Address& addr )

{

return std::tr1::shared_ptr<Person>(new RealPerson(name, birthday,addr));

}

结论

减少耦合行的惯用的思路是用声明依赖替代定义依赖。有2种方法:Interface classesHandle classes.

任何减少耦合的做法都是会带来负面影响的:占用内存增加,间接调用。但是关键还是在于引用本身是不是对这些额外的开销敏感。

posted on 2010-03-22 11:19 amenglai 阅读(394) 评论(0)  编辑  收藏 所属分类: Effecitive C++读书笔记


只有注册用户登录后才能发表评论。


网站导航: