【C++】动态内存管理(二)以对象管理资源

来源:互联网 发布:云计算应用场景举例 编辑:程序博客网 时间:2024/06/13 21:34

为什么要用对象去管理资源?
考虑如下情况。
我们声明一个对象A:

class A {private:    int value;public:    A(int v):value(v) {};    A() {        cout << "creat A" << endl;    }    ~A()     {        cout << "delete A" << endl;    };};

然后在一个函数中使用:

    void test()     {        A *a = new A(1);        ......        delete a;    }

可以看到,newdelete中间还有很多的执行语句,如果在这些执行语句中,触发了一个条件导致了return的提前出现,然后这个函数就会被提前中止,delete就不会被执行。

这看起来妥当,但在若干情况下,或许因为”…”中的某个过早的return语句。如果这样一个return语句被执行;又或者是因为”…”中有异常抛出;这些情况发生吃,那么控制流一定就不会触及到delete语句;而之前申请的内存就无可避免的泄露了。

当然了,谨慎的编写程序可以防止这一类错误。但是有个问题是必须值得思考的,随着业务逻辑的更改,这段代码很可能会在时间渐渐过去之后被修改。一旦软件开始接受维护,很有可能会有某些人添加return语句或者continue语句而未能全然领悟它对函数资源管理策略造成的后果。

这时候,我们需要将资源来视作一个对象来看。当构造初始化的时候获取资源,当析构销毁的时候释放资源。而析构函数是依赖C++的自动调用机制,这样我们就可以确保资源被释放。
—《Effective C++》

这会导致怎样的后果?
a指向的内存将没有任何方式可以进行访问,a指向的对象将不会被正确的析构。而且,其依然占据着堆上的内存空间和其内部保存的指针指向的内存空间,这就导致了严重的内存泄漏内存浪费
为了确保a指向的内存一定会被释放掉,我们需要将资源放进对象内部,然后当控制流离开test()函数,该对象的析构函数就会被自动的调用然后去释放那些资源。

怎样去做?获得资源后立刻就将对象放进管理对象(这也被称作RAII)。
我们试着去自己构造一个资源管理对象。
如下:

    template<typename T>    class Data    {    private:        T* pointer;    public:        Data(const Data& d) :pointer(d.pointer) {};        Data() :pointer(NULL) {};        Data(T* p=NULL) :pointer(p) {};        ~Data()         {            delete pointer;        }        T& operator*()        {            return *(this->pointer);        }    };

可以看到,这是一个简单的保存数据内存的管理类,当析构的时候就会自动删除其管理的内存空间。
我还重载了其“*”操作符,用以方便的资源操作。
下面是一个使用的示例:

    void test()    {        Data<int>p(new int(3));        cout << *p << endl;    }

在资源被拿出的一瞬间,我们就将这个资源放入了资源管理类中进行保存,由于p存在于栈空间,所以当test()结束的时候,p就会被释放,其析构函数就会被调用。
所以无论是什么原因,p所管理的资源都会被成功的释放掉。

不同的设计也对应着不同的资源类型,当我们使用不同的资源管理类的时候要牢记这一点。
对于下面的三种类型,我们在写拷贝构造和赋值操作的时候就一定要注意不同的写法。

  1. 如果是普遍性的资源(如stringvector),在赋值的时候应该是资源的拷贝。(生成资源副本进行拷贝)
  2. 如果是文件、连接设备这样的资源,在赋值的时候应该是资源支配权的转移。(转移内部资源指针所指向的资源的所有权)
  3. 如果是socket这样的资源,在赋值的时候应该是增加资源的引用数,当引用数为0的时候释放资源。
对于这三种的类型如何实现拷贝的控制,我就单开一章描写
原创粉丝点击