当在编写代码中用到异常,非常重要的一点是:“如果异常发生,程序占用的资源都被正确地清理了吗?”

大多数情况下不用担心,但是在构造函数里有一个特殊的问题:如果一个对象的构造函数在执行过程中抛出异常,那么这个对象的析构函数就不会被调用。

困难的事情是在构造函数中分配资源。如果在构造函数中发生异常,析构函数将没有机会释放这些资源。

这个问题经常伴随着”悬挂“指针出现。

例如:

// Naked pointers.
#include <iostream>
#include <cstddef>
using namespace std; class Cat
{
public:
Cat() {cout << "Cat()" << endl;}
~Cat() {cout << "~Cat()" << endl;}
};
class Dog
{
public:
void * operator new(size_t sz)
{
cout << "allocating a Dog" << endl;
throw ;
}
void operator delete(void * p)
{
cout << "deallocating a Dog" << endl;
::operator delete(p);
}
};
class UseResources
{
Cat * bp;
Dog * op;
public:
UseResources(int count = )
{
cout << "UseResources()" << endl;
bp = new Cat[count];
op = new Dog;
}
~UseResources()
{
cout << "~UseResources()" << endl;
delete [] bp;
delete op;
}
}; int main()
{
try
{
UseResources ur();
}
catch(int)
{
cout << "inside handler" << endl;
}
return ;
}

程序输出为:
UseResources()
Cat()
Cat()
Cat()
allocating a Dog
inside handler

程序的执行流程进入了UseResources的构造函数,Cat的构造函数成功地完成了创建对象数组中的三个对象。然而,在Dog::operator new()函数中抛出了一个异常。

程序在执行异常处理器之时突然终止,UseResources的析构函数没有被调用。这是正确的,因为UseResources的构造函数没有完成,但是,这也意味着,在堆上创建的Cat对象不会被销毁。

为了防止资源泄漏,有两种解决方法:

1.在构造函数中捕获异常,用于释放资源

2.在【对象】的构造函数中分配资源,并且在【对象】的析构函数中释放资源。(使资源成为对象)

这里我们探讨第二种方法,由于资源分配成为局部对象生命周期的一部分,如果某次分配失败了,那么栈解退的时候,其他已经获得所需资源的对象能够被恰当地清理。

这种技术成为“资源获得式初始化”,因为它使得对象对资源控制的时间与对象的生命周期相等。

为了达到上述目标,我们使用模版修改前一个例子:

// Safe, atomic pointers
#include <iostream>
#include <cstddef>
using namespace std; // Simplified. Yours may have other arguments.
template<class T, int sz = >
class PWrap
{
T * ptr;
public:
class RangeError{}; // Exception class
PWrap()
{
ptr = new T[sz];
cout << "PWrap constructor" << endl;
}
~PWrap()
{
delete [] ptr;
cout << "PWrap destructor" << endl;
}
T & operator[](int i) throw(RangeError)
{
if(i >= && i < sz)
{
return ptr[i];
}
throw RangeError();
}
};
class Cat
{
public:
Cat()
{
cout << "Cat()" << endl;
}
~Cat()
{
cout << "~Cat()" << endl;
}
void g() {}
};
class Dog
{
public:
void * operator new[](size_t)
{
cout << "Allocating a Dog" << endl;
throw ;
}
void operator delete[](void * p)
{
cout << "Deallocating a Dog" << endl;
::operator delete[](p);
}
}; class UseResources
{
PWrap<Cat, > cats;
PWrap<Dog> dog;
public:
UseResources()
{
cout << "UseResources()" << endl;
}
~UseResources()
{
cout << "~UseResources()" << endl;
}
void f()
{
cats[].g();
}
}; int main()
{
try
{
UseResources ur;
}
catch(int)
{
cout << "inside handler" << endl;
}
catch(...)
{
cout << "inside catch(...)" << endl;
}
return ;
}

程序输出为:

Cat()
Cat()
Cat()PWrap constructor
allocating a Dog
~Cat()
~Cat()
~Cat()
PWrap destructor
inside handler

程序为Dog对分配存储空间的时候再一次抛出了异常,但是这一次Cat数组中的对象被恰当的清理了,没有出现内存泄漏。

这里使用模版来封装指针的方法与第一种方法的区别在于,这种方法使得每个指针都被嵌入到对象中。【在调用UseResources类的构造函数之前这些对象的构造函数首先被调用】
,并且如果它们之中的任何一个构造函数在抛出异常之前完成,那么这些对象的析构函数也会在栈解退的时候被调用。

由于在一个典型的C++程序中动态分配内存是频繁使用的资源,所以C++标准中提供了一个RAII封装类,用于封装指向分配的堆内存的指针。

这就使得程序能够自动释放这些内存。auto_ptr类模版是在头文件<memory>中定义的,它的构造函数接受一个指向类属类型的指针作为参数。

auto_ptr类模版还重载了指针运算符*和->,一边对持有auto_ptr对象的原始指针进行运算。

下面代码演示了如何使用auto_ptr:

// Illustrates the RAII nature of auto_ptr
#include <memory>
#include <iostream>
#include <cstddef>
using namespace std; class TraceHeap
{
int i;
public:
static void * operator new(size_t siz)
{
void * p = ::operator new(siz);
cout << "Allocating TraceHeap object on the heap " << "at address " << p << endl;
return p;
}
static void operator delete(void * p)
{
cout << "Deleting TraceHeap object at address " << p << endl;
::operator delete(p);
}
TraceHeap(int i) : i(i) {}
int getVal() const {return i;}
}; int main()
{
auto_ptr<TraceHeap> pMyObject(new TraceHeap());
cout << pMyObject->getVal() << endl; return ;
}

程序输出为:

Allocating TraceHeap object on the heap at address 0x7a1768

5

Deleting TraceHeap object at address 0x7a1768

TraceHeap类重载了new运算符和delete运算符,这样,就可以准确地看到程序运行过程中发生了什么事情。
最重要的一点是,尽管程序没有显式地删除该原始指针,但是在栈解退的时候,pMyObject对象的析构函数会删除该原始指针。

RAII(Resource Acquisition Is Initialization)资源获得式初始化的更多相关文章

  1. RAII(Resource Acquisition Is Initialization)简介

    RAII(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种管理资源.避免泄漏的惯用法.C++标准保证任何情况下,已构造的 ...

  2. Resource Acquisition Is Initialization(RAII Idiom)

    原文链接:http://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Resource_Acquisition_Is_Initialization Intent ...

  3. Constructor Acquires, Destructor Releases Resource Acquisition Is Initialization

    w https://zh.wikipedia.org/wiki/RAII RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的 ...

  4. 平台支持的从经典部署模型到 Azure Resource Manager 的 IaaS 资源迁移

    本文介绍如何才能将基础结构即服务 (IaaS) 资源从经典部署模型迁移到 Resource Manager 部署模型. 用户可以阅读有关 Azure Resource Manager 功能和优点的更多 ...

  5. 导出resource文件的的资源

    写个小工具,方便一次性将resource文件中的资源导出,不然反编译后一个个找,真是太麻烦了. using System;using System.Collections.Generic;using  ...

  6. IdentityServer4之Resource Owner Password Credentials(资源拥有者密码凭据许可)

    IdentityServer4之Resource Owner Password Credentials(资源拥有者密码凭据许可) 参考 官方文档:2_resource_owner_passwords ...

  7. 性能测试-Linux资源监控⽅式

    Linux资源监控⽅式 1. 命令 2. 第三⽅⼯具(nmon) 3. LR(需要安装RPC相应服务包和开启服务)(略)   ⼀.命令 ⽅式 1. top (系统资源管理器) 2. vmstat (查 ...

  8. C++ 对象没有显式初始化

    C++ 对象没有显式初始化,结果是什么? 首先考虑非静态对象 1.方法内的局部对象: a.类类型:调用default构造方法 b.基本类型:值不确定 2.类中的数据成员: a.类类型:调用defaul ...

  9. Compute Resource Consolidation Pattern 计算资源整合模式

    Consolidate multiple tasks or operations into a single computational unit. This pattern can increase ...

随机推荐

  1. >/dev/null 2>&1 这句话的含义

    1表示标准输出,2表示标准错误输出 2>&1表示将标准错误输出重定向到标准输出,这样,程序或者命令的正常输出和错误输出就可以在标准输出输出(也就是一起输出). 一般来讲标准输出和标准错误 ...

  2. APNs改动 (转)

    对 APNs 的吐槽 APNs 是 Apple Push Notification service 的简称(注意 APNs 的大小写, s不需要大写). 以下是我收集的一些关于 APNs 的吐槽,你先 ...

  3. hdoj 1686 kmp

    题目:   Sample Input 3 BAPC BAPC AZA AZAZAZA VERDI AVERDXIVYERDIAN   Sample Output 1 3 0     代码:   #in ...

  4. 挖潜无极限———数据挖掘技术与应用热点扫描[ZZ]

    “我们把世界看成数学,并且把你也看成数学”——用这句话来说明数据挖掘技术的复合性和应用的广泛性似乎再好不过.如今,虽然一些行业在应用这一技术上仍然缺乏足够的主动,但一个不能阻挡的趋势是:已经有越来越多 ...

  5. 【POJ2482】【线段树】Stars in Your Window

    Description Fleeting time does not blur my memory of you. Can it really be 4 years since I first saw ...

  6. CentOS中vsftp安装、配置、卸载

    1. 安装VSFTP 1 [root@localhost ~]# yum -y install vsftpd 2. 配置vsftpd.conf文件 [root@localhost ~]# vi /et ...

  7. 网站开发常用jQuery插件总结(三)拖拽插件gridster

    1.gridster插件功能 实现类似于win8 磁贴拖拽的功能 2.gridster官方地址 http://gridster.net/ 在官方的网站上也有插件的帮助和实例,但是按照官方的说明,我在本 ...

  8. Python学习的一些好资料

    教程: 1. 廖雪峰的Python教程:http://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a0 ...

  9. utf8_general utf8_general utf8_bin区别

    对与general来说 ß = s 是为true的 但是对于unicode来说 ß = ss 才是为true的, 其实他们的差别主要在德语和法语上,所以对于我们中国人来说,一般使用general,因为 ...

  10. Android中的Selector的用法

    转自: Android中的Selector主要是用来改变ListView和Button控件的默认背景.其使用方法可以按一下步骤来设计: (以在mylist_view.xml为例) 1.创建mylist ...