昨天看到一个知识点觉得挺有意思的,而且自己还不是很清楚,觉得讲得很好。主题是“以对象来管理资源”
C++中用得最多的就是动态的内存分配,程序中的大部分bug也都是源自于内存泄露,这也是C++相较于其他高级语言更复杂的主要原因之一,不过考虑到它强大的功能和超高的效率,这样的复杂度也是应该的,所以上天是公平的,有点扯远了。内存只是我们必须管理的资源之一,其他常用到的资源还包括文件描述符,数据库连接,socket等,不论是哪种资源,我们用完之后必须要归还给操作系统,否则就会出现Memory Leak
看下面一段代码:
#include <iostream>
using namespace std;
class Widget
{
public:
Widget()
{
cout<<"Widget()"<<endl;
}
~Widget()
{
cout<<"~Widget()"<<endl;
}
};
Widget* CreateWidget()
{
Widget* pA = new Widget;
return pA;
}
void MaybeMemoryLeak()
{
Widget* p = CreateWidget();
//....
//....
//....
if(p!=NULL)
return;
delete p;
}
int main()
{
MaybeMemoryLeak();
cout << "Hello world!" << endl;
return 0;
}
乍一看,这段代码没有什么问题,仔细一想,这段代码确实有问题,而且问题很大。在函数“MaybeMemoryLeak()”中极有可能发生内存泄露,这段代码执行下来析构函数没有被调用,也就是发生了内存泄露。原因就在于delete p之前函数已经返回了,所以delete p不会执行。
有人肯定会说只要保证在delete p之前不执行return语句就可以了。貌似是这样,但是如果程序都如我们所预想的逻辑那样跑,那也就没有bug一说了。即使我们在delete p之前保证没有return语句,那么第二种可能是delete p语句位于循环内部,然而循环可能在delete p之前执行了continue语句或break语句而提前退出,这样势必也会发生内存泄露。有人说我也能保证这种情况不会发生,那么好吧,假如delete p语句之前的代码发生了异常,那么delete p语句仍然执行不到,我相信任何人没有敢拍着胸脯说他写的代码不会发生异常吧。
所以这几种情况造成了我们动态分配的内存很容易发生不能正确回收,从而造成内存泄露。
好了,下面进入本文真正的主题,以对象来管理资源。
根据对上面代码的分析,为了确保函数CreateWidget()返回的资源总是能被正确释放,我们需要将分配的资源放进对象内,当程序执行流离开MaybeMemoryLeak()时,该对象的析构函数会自动释放那些资源,歧视我的理解是这样的,对象在函数里就是一个局部变量,无论如何,函数结束该局部变量肯定要被释放,释放时又必然会调用该对象的析构函数。正是利用了这一点,我们可能将资源放在对象里,这样我们便可以来c++的”析构函数自动调用机制”确保资源被正确释放。
C++标准程序库提供的auto_ptr正是针对这种形势而设计的,auto_ptr是一个类指针对象,也可以理解为一个栈对象,其析构函数自动对其所指对象调用delete,注意是delete,而不是delete [];
所以我们可能将上面的代码加以修改:
#include <iostream>
#include <memory>
using namespace std;
class Widget
{
public:
Widget()
{
cout<<"Widget()"<<endl;
}
~Widget()
{
cout<<"~Widget()"<<endl;
}
};
Widget* CreateWidget()
{
Widget* pA = new Widget;
return pA;
}
void MaybeMemoryLeak()
{
auto_ptr<Widget> p(CreateWidget());
//....
//....
//....
cout<<p.get()<<endl;
}
int main()
{
MaybeMemoryLeak();
cout << "Hello world!" << endl;
return 0;
}
下面是程序的执行效果
可以看到,虽然我们没有用delete p这样的语句来释放p,,但是析构函数仍然被调用,原因就是当对象p在函数MaybeMemoryLeak()返回时自动销毁而对其所指对象执行delete操作。
这里只是说明了为什么要以对象来管理资源,否则很容易出现MemoryLeak,具体以对象来管理资源的方法就是用到了智能指针,关于智能指针的用法和注意点这里就不多说了。比如说像auto_ptr复制时不能保值,所以在赋值操作时要特别注意。还有就是auto_ptr不能用作STL容器中的元素等等。