zoukankan      html  css  js  c++  java
  • RAII(Resource Acquisition Is Initialization)资源获得式初始化

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

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

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

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

    例如:

    // 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 47;
        }
        void operator delete(void * p)
        {
            cout << "deallocating a Dog" << endl;
            ::operator delete(p);
        }
    };
    class UseResources
    {
        Cat * bp;
        Dog * op;
    public:
        UseResources(int count = 1)
        {
            cout << "UseResources()" << endl;
            bp = new Cat[count];
            op = new Dog;
        }
        ~UseResources()
        {
            cout << "~UseResources()" << endl;
            delete [] bp;
            delete op;
        }
    };
    
    int main()
    {
        try
        {
            UseResources ur(3);
        }
        catch(int)
        {
            cout << "inside handler" << endl;
        }
        return 0;
    }
    View Code

    程序输出为:
    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 = 1>
    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 >= 0 && 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 47;
        }
        void operator delete[](void * p)
        {
            cout << "Deallocating a Dog" << endl;
            ::operator delete[](p);
        }
    };
    
    class UseResources
    {
        PWrap<Cat, 3> cats;
        PWrap<Dog> dog;
    public:
        UseResources()
        {
            cout << "UseResources()" << endl;
        }
        ~UseResources()
        {
            cout << "~UseResources()" << endl;
        }
        void f()
        {
            cats[1].g();
        }
    };
    
    int main()
    {
        try
        {
            UseResources ur;
        }
        catch(int)
        {
            cout << "inside handler" << endl;
        }
        catch(...)
        {
            cout << "inside catch(...)" << endl;
        }
        return 0;
    }
    View Code

    程序输出为:

    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(5));
        cout << pMyObject->getVal() << endl;
    
        return 0;
    }
    View Code

    程序输出为:

    Allocating TraceHeap object on the heap at address 0x7a1768

    5

    Deleting TraceHeap object at address 0x7a1768

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

  • 相关阅读:
    【转载】为什么我的网站加www是打不开的呢
    【转载】IIS网站配置不带www域名直接跳转带www的域名
    【转载】IIS网站如何同时解析带www和不带www的域名
    【转载】C#中将字符串分割成字符数组
    Android面试,简要介绍一下asynctask和handler的优缺点
    Android面试题(2)
    Android面试题(1)
    Android -- 与WEB交互在同一个会话Session中通信
    25匹马的角逐
    Android -- 检测耳机插入状态
  • 原文地址:https://www.cnblogs.com/pingge/p/3439381.html
Copyright © 2011-2022 走看看