您的位置:首页 > 编程语言 > C语言/C++

细读《Effective C++》之三

2007-05-29 13:44 323 查看

Chapter 3. Resource Management

Scott说:这儿的resource包括dynamically allocated memory、file descriptors、mutex locks、GUI objects、database connections和network sockets。

“拿了我的给我还回来,吃了我的给我吐出来”……

Item 13 - 17

条款13:Use objects to manage resources


void f()




...{


Investment *pInv = createInvestment(); // call factory function


... // use pInv


delete pInv; // release object


}

为了防止程序在delete之前return或因exception中断而无法调用delete,可使用smart pointer auto_ptr(a pointer-like object, its destructor automatically calls delete on what it points to):


// Resource Acquisition Is Initialization(RAII)


void f()




...{


std::auto_ptr<Investment> pInv(createInvestment()); // call factory function


... // use pInv as before


} // automatically delete pInv via auto_ptr's dtor

auto_ptr的初始化(也可以用assignment)和resource的分配在同一条语句中完成,其destructor保证了资源会被release。为了保证在任意时刻只有一个auto_ptr指向同一个资源(这也是其缺陷),当一个auto_ptr被copy给另一auto_ptr对象之后,前一个将被置为null,这很安全,却也很麻烦。所以在使用auto_ptr指针时要注意copy操作。

如果希望多个指针指向同一资源,可使用引用计数智能指针(reference-counting smart pointer, RCSP)TR1的tr1::shared_ptr指针。

auto_ptr和tr1::shared_ptr都不能用于动态分配数组,因为二者都没有delete[]的用法。boost::scoped_array和boost::shared_array可以分配数组。

条款14:Think carefully about copying behavior in resource-managing classes

条款13主要针对heap-based的资源进行管理,不是所有资源都是heap-based,如mutex。


void lock(Mutex *pm); // lock mutex pointed to by pm


void unlock(Mutex *pm); // unlock the mutex

借用条款13的RAII思想,设计class Lock:


class Lock : private Uncopyable // prohibit copying




...{


public:


explicit Lock(Mutex *pm) : mutexPtr(pm)




...{ lock(mutexPtr); } // acquire resource




~Lock() ...{ unlock(mutexPtr); } // release resource




private:


Mutex *mutexPtr;


};

对于申请类似critical resources,同样可借用RCSP的思想实现,对critical resources的申请和分配进行计数。这里的一个问题是在tr1::shared_ptr析构时如果counter为0将delete掉mutex,因此可以使用function object实现如下:




class Lock ...{


public:


explicit Lock(Mutex *pm) // init shared_ptr with the Mutex


: mutexPtr(pm, unlock) // to point to and the unlock func




...{ // as the deleter


lock(mutexPtr.get()); // see Item 15 for info on "get"


// no longer declares a destructor


}




private:


std::tr1::shared_ptr<Mutex> mutexPtr; // use shared_ptr


}; // instead of raw pointer

条款15:Provide access to raw resources in resource-managing classes

为了能使资源顺利释放,Scott提倡我们使用auto_ptr和tr1::shared_ptr,友情提示:智能指针auto_ptr和tr1::shared_ptr不是指针,而是pointer-like object。因此如果要访问原始资源,需要从其中取出。


std::tr1::shared_ptr<Investment> pInv(createInvestment()); // from Item 13


int daysHeld(const Investment *pi); // return number of days investment has been held


int days = daysHeld(pInv.get()); // fine, passes the raw pointer in pInv to daysHeld

说白了,使用auto_ptr和tr1::shared_ptr时,记得它们是对象就行了。

犹如在使用string时,为了得到字符串,你需要这样去用:


string str("Hello");


char *pstr = str.c_str();

除了类似的explicit convertion,也有implicit convertion:




class Font ...{


public:


...




operator FontHandle() const ...{ return f; } // implicit conversion function


...


};




Font f(getFont());


int newFontSize;


...


changeFontSize(f, newFontSize); // implicitly convert Font to FontHandle

诚如Scott所言:The best design is likely to be the one that adheres to make interfaces easy to use correctly and hard to use incorrectly.

至于对原始资源访问所造成的contrary to encapsulation,则可以通过design使client取其所取,避其所避。

对于C/C++程序员来讲,诸如“{”和“}”,“[”和“]”,“"”和“"”,Get和Release,malloc和free,new和delete这样的搭配我们早已习惯,而对于借助对象的constructor和destructor来完成资源“自动”的分配和释放这样的安全操作显然还需要时间去适应。

看到这一款,我越来越觉得我的C++基础需要进一步加强。不知道Scott新加的这第三章内容是过于艰涩,还是怎么,总觉得似乎简单,但又没有那么简单,理解的不够深刻。

条款16:Use the same form in corresponding uses of new and delete

这一款在第二版时也是有的,第三版还是加了点内容(尽管不多,却让内容更加易于理解),首先对Scott的态度表示尊敬。

new和delete不仅仅是要成对出现的问题(当然,这是最根本最重要的问题),而更是要正确的成对出现,就像{}一样。当new被使用时,发生了两件事:内存分配和constructor(s)调用。当delete被使用时,也发生了两件事:destructor(s)调用和内存回收。问题是你用了什么就要还回什么,你用了多少就要还回多少。

因此,Scott说:“如果你调用new时用了[],调用delete时也要用[]。如果调用new时没有用[],那调用delete时也不要用[]”。


typedef string addresslines[4];




string *pal = new addresslines; // 这个typedef使用的太有创意了!


delete pal; // 错误!


delete [] pal; // 正确

条款17:Store newed objects in smart pointers in standalone statements


int priority();


void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);




// tr1::shared_ptr's constructor taking a raw pointer is explicit


processWidget(new Widget, priority()); // error!


processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority()); // OK!

对于初次看到Scott的Resource Management这一章的coders,我不知道大家看过之后能领会多少,反正我自己心里没有底,各种智能指针的使用,想必也和个人的智商成一定正比。总觉得看过之后,再去写代码,难保不是邯郸学步。

这时候,Scott又说:although we're using object-managing resources everywhere here, this call may leak resources……

Before processWidget can be called, then, compilers must generate code to do these three things:

1) Call priority.

2) Execute "new Widget".

3) Call the tr1::shared_ptr constructor.

然而,the call to priority can be performed first, second, or third. 如果不幸的成了这样:

1) Execute "new Widget".

2) Call priority.

3) Call the tr1::shared_ptr constructor.

如果更加不幸的是:the call to priority yields an exception。不要因为这种可能只有不到0.01%,如果被你的客户和老板抓到,那就是100%。


std::tr1::shared_ptr<Widget> pw(new Widget); // store newed object in a smart pointer in a standalone statement


processWidget(pw, priority()); // this call won't leak

Scott建议:Store newed objects in smart pointers in standalone statements. Failure to do this can lead to subtle resource leaks when exceptions are thrown.

粗略地看完这一章,Scott带我们实现了Resources Management:

1) Use objects(smart pointers objects) to manage resources: auto_ptr, tr1::shared_ptr and boost::shared_array;

2) Think carefully about copying behavior in resource-managing classes: prohibit copying or reference-count, delete or unlock;

3) Provide access to raw resources in resource-managing classes: explicit or implicit, safer or more convenient;

4) Use the same form in corresponding uses of new and delete: new and delete, new [] and delete [];

5) Store newed objects in smart pointers in standalone statements: avoid exception thrown between new and copy to tr1::shared_ptr.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: