C++内存管理 RAII

    C++和C#,python等语言不同,其本身是没有gc机制的,因此我们需要手动来管理内存,一般来说,我们只要将每一个new出来的对象delete之后,就可以保证资源不被泄漏,但万事都不是那么理想的,我们可能因为忘记delete而导致内存泄漏,并且当开发大型项目时,我们可能不了解其他人的代码意图,导致不能正确释放内存,因此,我们有几种方法来解决该问题。
    RAII即当资源取得时机就是初始化时机,也就是以内存管理对象。我们首先要明白我们的程序中的变量存放在什么位置,一般来说,我们创建的对象会放在堆中、栈中、静态区中。栈中的对象是由系统来管理的,比如局部变量会在作用域离开局部时被销毁。但堆中的内存是由程序员手动控制的,因此我们要管理好的主要是堆中的内存,我们来看以下代码

void test(){
    Point *p = createPoint();
    ...
    delete p;
}

createPoint()函数返回一个Point类型的指针,我们在堆中创建的指针p指向createPoint()返回的指针,我们在…程序块中有可能提前return,导致createPoint()返回的指针资源没被删除,占用了内存导致内存泄漏。我们知道实例化一个类有两种方法,一是在栈中创建,也就是Point p,二是在堆中创建,也就是Point *p = new Point();接下来我们看一个例子。

Point* test(){
    Point p;

    return &p;
}

该函数返回一个Point类型的指针,但这种写法是错误的,因为我们是在栈中创建的对象p,p由系统管理,当该函数执行完毕后,p被销毁,那么test()函数就返回了一个已经不存在的指针,便会导致错误。

Point* test(){
    Point *p = new Point();

    return p;
}

对于上述函数,我们要了解,p是一个局部变量,是在栈中分配的4个字节的地址,它里面保存的是在堆中创建的new Point()数据的地址,此时我们将p返回出去,也就是将在堆中创建的Point数据的地址给传出去,我们仍然可以访问之前我们在堆中创建的Point数据。我们画一个图帮助我们形象理解
这里写图片描述
那么 return p即返回的0x0002a,也就是new Point()这个数据存放的地址,这样我们就可以对我们在堆中申请的内存进行操作。
    当我们使用RAII时我们要明确当我们管理的资源需要拷贝时我们需要作出什么行为。一是禁止拷贝,此时我们可以使用class Point:public Uncopyable{};来禁止拷贝行为,具体可参见《Effecitive C++》条款6。二是我们可以使用引用计数,每当有指针指向该资源,引用计数+1,当没有指针指向该资源时,就将其删除。C++引入了shared_ptr来实现该功能,但shared_ptr无法解决环状引用的问题,shared_ptr还可以指定删除器,当释放资源时执行删除器,比如我们可以在删除器中指定文件关闭,解除互斥锁等操作。
    shared_ptr在大多数情况下可以解决我们的问题,但有时我们需要自定义一些行为时可以创建自己的资源管理类。如下所示

#include <iostream>

using namespace std;
class File{

};

class Uncopyable
{
protected:                                   // allow construction
    Uncopyable() {}                       // and destruction of
    ~Uncopyable() {}                     // derived objects...
private:
    Uncopyable(const Uncopyable&);             // ...but prevent copying
    Uncopyable& operator=(const Uncopyable&);
};

class UncopyableExample: private Uncopyable
{
};

class FileHandle:public Uncopyable{
public:
    FileHandle(File *file):m_pFile(file){
        cout << "打开文件" << endl;
    }
    ~FileHandle(){
        if(m_pFile != nullptr)
        {
            delete m_pFile;
            m_pFile = nullptr;
            cout << "关闭文件" << endl;
        }
    }

private:
    File *m_pFile;
};


int main(int argc, char *argv[])
{
    FileHandle f(new File());
    return 0;
}

File类是文件类,Uncopyable类可用于阻止FileHandle类的拷贝行为,FIleHandle类用于管理File,在实例化时打开文件,在析构时关闭文件,保证资源得到释放。利用shared_ptr指定删除器的版本如下:

class File{

};

void closeFile(File *file){
    cout << "关闭文件" << endl;
}

int main(int argc, char *argv[])
{
    shared_ptr<File> p(new File(), closeFile);
    return 0;
}

    总结以上情况,只要我们能理解好RAII就能正确做好内存管理,避免出现内存泄漏等问题。

猜你喜欢

转载自blog.csdn.net/wingwc/article/details/78852895