条款13:以对象管理资源
我们为什么要以对象管理资源呢?有什么用处呢?在回答这个问题之前先来看看如下代码:
class Investment{...};
Investment* createInvestment();
void f(){
Investment* pInv=createInvestment();
...
delete pInv;
}
可以看到,上述代码中,如果…中代码提前return掉,或者…中发生异常,甚至createInvestment()中由于循环中的goto语句或者continue语句的设计异常,这样都将导致delete pInv语句不会被执行,这样将引发内存泄露等一系列问题。
为了解决上述问题,我们需要将资源放入对象内,当控制流离开f函数时候,对象那个的析构函数自动释放资源,即
1)获得资源后立即放进资源管理对象中;
2)管理对象运用析构函数确保资源被释放,即不论何时,控制流离开区块时候,其析构函数会自动调用,于是资源被释放掉!
这样的解决思路是不是简洁明了呀!
我们通常用到的有两种这样的对象,可以在控制流离开区块或者函数时候被释放,包括auto_ptr和auto_ptrs。
1)auto_ptr是个“类指针”对象,也就是所谓的“智能指针”,气息狗函数自动对其所指的对象那个调用delete,请看如下代码示意:
void f(){
std::auto_ptr<Investment> pInv(createInvestment());
...
}
这样就可以使得该指针在离开作用域时候自动调用delete,自动调用析构函数!
PS:auto_ptr被销毁的时候会自动删除他所指向的对象,因此注意不要让多个auto_ptr对象指向同一个对象,如果这样做的话,对象会被删除一次以上,而这样会使得你的程序出现“未定义行为”的错误!为了预防这个问题,C++的设计者为auto_ptr设计有一个不寻常的性质,若通过copy构造函数或者copy assignment操作符赋值它们,它们会变为null,而复制所得的函数将取得资源的唯一所有权!具体参看如下代码:
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
class Hello{
public:
Hello(){
}
Hello(int x, int y) :x(x), y(y){
}
Hello(Hello& h){
this->x = h.x;
this->y = h.y;
}
Hello& operator=(const Hello& h){
this->x = h.x;
this->y = h.y;
return *this;
}
friend ostream& operator<<(ostream& os, Hello& hel){
os << hel.x << " " << hel.y << endl;
return os;
}
void show(){
cout << x << " " << y << endl;
}
friend Hello* createHello(Hello& hello){
try{
Hello *hel = new Hello();
hel->x = hello.x;
hel->y = hello.y;
return hel;
}
catch (exception &e){
cout << e.what() << endl;
}
return NULL;
}
~Hello(){
}
private:
int x;
int y;
};
/*int* h1(){
int m = 9;
return &m;
}*/
int main(){
Hello hel(1, 2);
auto_ptr<Hello> hel_ptr(createHello(hel));
auto_ptr<Hello> hel_ptr2(hel_ptr);
cout << *hel_ptr2<< endl;
hel_ptr = hel_ptr2;
cout << *hel_ptr << endl;
}
这里我们输出*hel_ptr2和*hel_ptr的值,注意它们都是通过别的制针值赋值的指针。
此时运行结果为:
如果我们将代码改为如下(PS:为了验证auto_ptr的copy函数的奇特属性)
#include <iostream>
#include <cstring>
#include <string>
using namespace std;
class Hello{
public:
Hello(){
}
Hello(int x, int y) :x(x), y(y){
}
Hello(Hello& h){
this->x = h.x;
this->y = h.y;
}
Hello& operator=(const Hello& h){
this->x = h.x;
this->y = h.y;
return *this;
}
friend ostream& operator<<(ostream& os, Hello& hel){
os << hel.x << " " << hel.y << endl;
return os;
}
void show(){
cout << x << " " << y << endl;
}
friend Hello* createHello(Hello& hello){
try{
Hello *hel = new Hello();
hel->x = hello.x;
hel->y = hello.y;
return hel;
}
catch (exception &e){
cout << e.what() << endl;
}
return NULL;
}
~Hello(){
}
private:
int x;
int y;
};
/*int* h1(){
int m = 9;
return &m;
}*/
int main(){
Hello hel(1, 2);
auto_ptr<Hello> hel_ptr(createHello(hel));
auto_ptr<Hello> hel_ptr2(hel_ptr);
cout << *hel_ptr<< endl;
hel_ptr = hel_ptr2;
cout << *hel_ptr2 << endl;
}
这里我们输出*hel_ptr和*hel_ptr2的值,注意它们都是被赋值的指针。
运行结果为:
可以发现,auto_ptr通过copying函数复制时候,它们会变为NULL,复制的指针将获得取得资源的唯一特权,简单理解就是在auto_ptr通过copying函数赋值时,左值获得资源,右值置为NULL!
2)auto_ptr的引用方案是“引用技术型智慧指针(reference-counting smart pointer;RCSP)”,持续追踪共有多少对象指向某笔资源,并在无人指向它时自动删除该资源,RCSPs提供的行为类似垃圾回收,但无法打破环状引用,参看如下代码:
void f(){
...
std::trl::shared_ptr<Investment> pInv(createInvestment());
std::trl::shared_ptr<Investment> pInv2(pInv);//pInv和pInv2指向同一个对象
pInv=pInv2;//pInv和pInv2指向同一个对象
...
}
需要注意的是,auto_ptr和trl::shared_ptr的析构函数做的是delete动作而不是delete[]动作,那意味着在动态分配时候得到的array身上使用auto_ptr或者trl::shared_ptr是个馊主意,但是仍然可以通过编译,运行报错。
总结:
1)为了防止资源泄露,请调用RAII对象,它们在构造函数中获得资源并在析构函数中释放资源;
2)通常我们使用auto_ptr和shared_ptr进行对象管理资源,但需要注意auto_ptr的copying函数的奇葩特性哈!
PS:函数createInvestment中返回指针一般超级容易忘记使用delete进行资源删除,因此我们需要对象管理这些资源,这里我们介绍了std::auto_ptr和std::trl::shared_ptr两种对象为例,做了讲解,大家也可以试试其他对象管理资源哈!