目录
这是effectiveC++中第三章内容:资源管理
在资源管理中小心copy行为
不是所有资源都是heap-based,对那种资源而言,像auto_ptr和 tr1 ::shared_ ptr这样的智能指针往往不适合作为资源掌管者(resource handlers),你需要建立自己的资源管理类。
例如,假设我们使用C API函数处理类型为Mutex的互斥器对象( mutexobjects),共有lock和unlock两函数可用:
再用一个基于RALL守则的class来管理这个mutex。
例如:
m12拷贝时会调用lock函数对Mutex对象进行加锁,但是m对象处于使用状态,因为其会阻塞等待,等待m1释放互斥量,但是m1此时也无法向下执行,所以这个程序就产生了死锁,所以对于我们设计的Lock这个类,我们不希望其有拷贝行为。
当RAII对象被复制时,我们通常有以下两种做法
1.禁止复制,当RAII对象被复制并不合理时。
2.对底层资源做引用计数,例如像shared_ptr。
它可以改变mutexPtr的类型,将它从 Mtex*改为tr1 ::shared ptr<Mutex>。然而很不幸tr1 : :shared_ptr的缺省行为是“当引用次数为0时删除其所指物”,我们想要的是解除锁定而不是删除。但shared_ptr是可以提供定制删除器的,也就是析构是怎样的方式去执行。
例如:get函数可以获取内部的原始指针
再次解释:
1.当引用计数为0时,我们希望做的是解锁资源而不是删除资源,但是我们的shared_ptr提供一个“删除器”,删除器可以是一个函数或函数对象,因此我们为mutexPtr提供了unlokc函数,当引用计数为0时就会调用unlock函数,将Mutex对象解锁。
2.不再设计析构函数:因为默认的析构函数会调用其非静态成员的析构函数,因此默认的析构函数会在互斥器的引用计数为0时自动调用shared_ptr的删除器(此处为unlock函数)
请记住:
1.为防止资源泄漏,请使用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源。为防止资源泄漏,请使用Raii对象,它们在构造函数中获得资源并在析构函数中释放资源.
2.复制RAII对象必须一并复制它所管理的资源,所以资源的copying行为决定RAII对象的copying行为。
3.普遍而常见的 RAII class copying行为是:抑制copying、施行引用计数法(reference counting)。不过其他行为也都可能被实现。
在资源管理中提供对原始资源的访问
使用智能指针如auto ptr或tr1 : :shared ptr保存factory函数如createInvestment的调用结果。
std::trl ::shared ptr<Investment> pInv (createInvestment());
该createInvestment()函数会返回一个Investment指针。直接让shared_ptr去管理。
假设你希望以某个函数处理Investment对象,例如:
int daysHeld(const Investment* pi) ;
所以你要将RALL对象中的原始数据拿出来,本例为底部之Investment* ,有显式转换和隐式转换。 显式转换和隐式转换两种做法
显示转换:
tr1 : :shared ptr和 auto_ptr都提供一个get成员函数,用来执行显式转换,也就是它会返回智能指针内部的原始指针(的复件)并且,tr1 : :shared_ptr和 auto_ptr也重载了指针取值(pointer dereferencing)操作符( operator->和operator*),它们允许隐式转换至底部原始指针。
看其中一种做法:
也可在自己创建的RAII类中,写一个get函数。例如:
FontHandle getFont(); //得到某种字体
void releaseFont(FontHandle fh);//释放字体
void changeFontSize(FontHandle f, int newSize); //改变字体大小
//FontHandle资源管理类
class Font
{
public:
explicit Font(FontHandle fh) :f(fh) {}
~Font() { releaseFont(f); }
FontHandle get()const
{
return f;
}
private:
FontHandle f;
};
API可这使用:
Font f(getFont());
int newFontSize;
changeFontSize(f.get(), newFontSize);
另外一种方法是提供隐式转换函数
隐式转换:
大家可以先看下这篇文章:有详细介绍隐式转换
例如:
operator FontHandle()const { return f; }
后面调用函数时会比较自然点:
Font f(getFont());
int newFontSize;
//f会自动调用隐式转换函数转换为FontHandle,然后返回类中的FontHandle对象,将其传入参数1
changeFontSize(f, newFontSize);
但这个隐式转换也会增加错误的机会。例如:
Font f1(getFont());
FontHandle f2=f1;
例如下面将f1中的内部资源拷贝给f2,但是如果f1被销毁了,那么f2就称为“虚吊的”(dangle)
请记住:
1.APIs往往要求访问原始资源(raw resources),所以每一个RAII class应该提供一个“取得其所管理之资源”的办法。API往往要求访问原始资源(原始资源),所以每一个Raii类应该提供一个“取得其所管理之资源”的办法。
2.对原始资源的访问可能经由显式转换或隐式转换。一般而言显式转换比较安全,但隐式转换对客户比较方便。
以独立语句将newed对象置入智能指针
假设我们有个函数用来揭示处理程序的优先权,另一个函数用来在某动态分配所得的 widget上进行某些带有优先权的处理:
int priority();
void processwidget (std::tr1 : :shared_ptr<widget> pw,int priority);
目前上面这条代码还不能编译通过,tr1 : :shared_ptr构造函数需要一个原始指针(raw pointer),但该构造函数是个explicit构造函数,无法进行隐式转换,将得自"newWidget"的原始指针转换为processwidget所要求的tr1 ::shared_ptr。
例如:
processWidget (std: :tr1: :shared_ _ptr<Widget> (new Widget), priority());
但上述的调用还可能出现资源泄漏的问题。原因:
编译器产出一个processwidget调用码之前,必须首先核算即将被传递的各个实参。上述第二实参只是一个单纯的对priority函数的调用,但第一实参std: :tr1 : : shared_ ptr<Widget>(new widget)由两部分组成:
C++ 编译器以什么样的次序完成这些事情呢?弹性很大。这和其他语言如Java和C#不同,那两种语言总是以特定次序完成函数参数的核算。可以确定的是"newwidget”一定执行于tr1 : :shared ptr构造函数被调用之前,但对priority的调用则可以排在第--或第二或第三执行。若先执行new语句,在执行priority,最后调用构造函数,如果priority出错了,那么资源就泄漏了。
解决方法:避免这类问题就是分离语句,将“创建的对象”与“放入智能指针对象”这两个步骤合成一步完成,而不是在函数调用中完成。
例如:
std::tr1::shared_ptr<Widget> pw(new Widget); //以单独语句存储对象
processWidget(pw, priority());
请记住:
1.以独立语句将newed对象存储于(置入)智能指针内。如果不这样做,一旦异常被抛出,有可能导致难以察觉的资源泄漏。