条款13:以对象来管理资源
在C++中我们经常会涉及到资源的申请与申请,一般都是由关键字new 和 delete来操作的,两者都是成对存在的,缺一不可,否则会出现意想不到的问题,例如:
class Investment{.....}; Investment* pinv = createInvestment();我们在使用完后要动态的释放掉pinv所指向的资源,例如在下面的函数中做了调用:
void f(){ Investment* pinv = createInvestment(); ... delete pinv; }正常情况下这将运行良好,但是如果在...中函数提前的返回了或者在跟特别的出现了异常程序异常的停止了,这是delete函数将
std::auto_ptr<Investment> pivn = createInvestment();这样当函数f执行结束的时候,auto_ptr离开它的作用域,将会自动的调用auto_ptr的析构函数,此时pinv所指向的资源也就得到了释放!
std::auto_ptr<Investment> pivn1(createInvestment());//pivn1指向createInvestment返回值 std::auto_ptr<Investment> pivn2(pivn1); //pivn2指向对象,pivn1被设置为NULL pivn1 = pivn2; ////pivn1指向对象,pivn2被设置为NULLauto_ptr有个不寻常的性质:若通过拷贝构造或赋值操作符复制它们,它们会变成null,而复制所得的指针将取得资源的唯一拥有权。
1.对于动态分配的array上是不能使用的,因为shared_ptr内部采用的是delete而不delete[];
2. 对于环状引用的情况也是不能处理的,此时应该采用weaked_ptr;
总之如果你的程序中有delete相关操作的出现,那就说明你的程序有随时出现意想不到情况出现的可能!
请记住:
条款14:在资源管理类中小心copying行为
在条款中主要的介绍了智能指针的用法,那里解决为问题是指向一个heap空间的指针的申请与释放问题,然而并非所有的资源都是在heap中申请的,这时候智能指针就不适合了,例如对于类型为Mutex的互斥器对象,只有lock和unlock的操作,在这里lock与unlock是成对存在的,为了防止调用lock后忘记unlock我们可以自己管理资源,例如:
class Lock{ public: explicit Lock(Mutex* pm):mutexPtr(pm){ lock(mutexPtr); } ~Lock(){ unlock(mutexPtr); } private: Mutex* mutexPtr; };
Mutex m; ..... { ...... Lock m1(&m) ...... }这样在m1离开作用域的时候,会自动的调用Lock的析构函数也就是Mutex的unlock函数解锁!
Lock m11(&m); //锁定m Lock m12(m11);//将m11复制到m12身上。这会发生什么事?
此时会怎样来处理?这个主要有以下几种方法:
1.禁止复制,有时候有些对象是不适合被复制的,对于一个想Lock这样的对象就是这样的情况,我们可以采用前面介绍的方法,
class Lock:private Uncopyable{ //禁止复制 public: .... };
class Lock{ public: explicit Lock(Mutex* pm):mutexPtr(pm,unlock){ lock(mutexPtr.get()); } private: tr1::shared_ptr<Mutex> mutexPtr; };在本例中不用再写析构函数,因为默认析构函数调用的时候,会自动的调用mutexPtr的析构函数,即为前面制定的unlock函数。
请记住:
条款15:在资源管理类中提供对原始资源的访问
前面几个条款主要的将了资源管理类对资源的管理,但是如果需要资源管理类的原始资源的时候该怎么做呢?例如:
class Investment{ public: bool isTexFree() const; ... };
std::tr1::shared_ptr<Investment> pinv = createInvestment();此时有个函数调用:
void dayHeld(Investment* ph);此时如果直接用dayHeld(pinv)是错误的,因为要求是指针!
void dayHeld(pinv.get());
bool isTrue = pinv->isTextFree(); bool isFalse = (*pinv).isTextFree();为了兼容性,我们一般在自己的资源管理类中也会定义get()函数来获得对原始资源的调用,例如:
class Font{ public: explicit Font(FontHandle fn):f(fn){} .... FontHandle get() const{ return f; } ... ~Font(){realseFont(f);} private: FontHandle f; };上面定义的Font类可以看做是FontHandle类的资源管理类,对于需要FontHandle类型的函数调用,我们可以通过Font类的get() 函数获得,与智能指针的用法几乎相同。此外还可以用隐式类型转化来替换get()函数调用,这种用法容易出现问题,建议不要使用
请记住:
条款16:当成对的使用new 和 delete时,要确保new 和 delete的格式是相同的
这个条款比较简单,但是却很容易出错,首先我们看下当我们使用new和delete的时候编译器为我们做了什么,当我们调用new操作符的时候,编译器会首先在内存中帮我们申请一块空间,然后调用对应对象的构造函数,想对应的当我们调用delete的时候,编译器会首先在该空间调用对应的析构函数然后再对该空间资源进行释放,例如:
std::string* p = new string[10];
delete [] p;如果我们采用了delete p的形式,将会出现不确定的结果,我们是通过[]符号来告诉编译器要析构的是一个对象还是一个对象的列表,对应的会调用一次析构函数或者多次析构函数,如果在单一对象上调用多次析构函数或者在多个对象上调用单次析构函数,后果可想而知!
typedef std::string AddressLine[10]; std::string* p = new AddressLine;此时我们在调用delete的时候一定要注意使用delelet[], 在C++中存在强大的容器类,如果应用恰当完全可以将C中引入的array数组替代掉,例如上面我们完全可以采用vector<string>的形式,这样就不用担心资源的释放问题了!
请记住:
条款17:以独立的语句将new的资源放入到智能指针中
考虑以下情况:
int prirority(); void processWidget(std::shared_ptr<Widget> pw, int priority);我们在对processWidget函数进行调用的时候,可以采用如下形式:
processWidget(std::tr1::shared_ptr<widget> pw(new widget), priority());注意我们不能直接将new widget作为实参传入std:shared_ptr<widget> pw形参中,在上面的函数调用中,看起来没有问题但是可能回出现内存泄露的情况,因为在函数中,参数的调用顺序会因为编译器的不同而不同的,例如上面的循序可能是:
std::tr1::shared_ptr<widget> pw(new widget); int pri = priority(); processWidget(pw,pri);
请记住: