1 C++ RAII机制

RAII(Resource Acquisition Is Initialization),资源获取即初始化,是一种C++编程技术。在类的构造函数中请求资源,在类的析构函数中释放资源的技术称为资源获取即初始化,简称RAII。

RAII技术可以将每个资源封装到一个类中,其中

  • 构造函数负责获取资源并建立所有类的不变量,如果无法完成则抛出异常
  • 析构函数负责释放资源

遵循此种技术规范,可以避免在普通代码中分配内存,把资源分配操作隐藏在构造函数与析构函数中,这样可以避免使用裸new和裸delete操作,使得我们的代码避免因为释放资源而导致内存泄漏的问题。

在C++标准库的设计中,譬如std::stringstd::vector等遵循RAII设计,另外标准库还提供了几个RAII包装器用于管理用户的资源,

  • std::unique_ptrstd::shared_ptr智能指针管理动态分配的内存,管理由普通指针表示的任何资源;
  • std::lock_guardstd::unique_lockstd::shared_lock用于管理互斥锁;

下面就是一个典型的遵循RAII机制的C++类代码,

#include <iostream>
#include <string>


class Example
{
public:
    Example(int size):m_DataSize(size),m_pData(new double[size])
    {
        for (int i = 0; i < size; ++i)
        {
            m_pData[i] = 0.0;
        }

        std::cout << "构造函数初始化资源" << std::endl;
    }

    virtual~ Example()
    {
        delete[] m_pData;

        m_DataSize = 0;

        std::cout << "析构函数释放资源" << std::endl;
    }

private:
    double* m_pData;
    int m_DataSize;
};

int main()
{
    Example example(5);

    return 0;
}

如果我们不使用RAII机制设计,会造成什么后果呢?我们看以下的代码

#include <iostream>
#include <string>
#include <assert.h>

void test()
{
    double size = 10;
    double* array = new double[10];
    for (int i = 0; i < size; ++i)
    {
        array[i] = 0.0;
    }

    assert(size == 11);

    delete[] array;
}


int main()
{
    test();

    return 0;
}

我们在上述代码中声明了一个10个大小的double型数组,然后给它所有的元素初始化为0.0,在test()函数的最后一行使用delete[] array使用数组内存,但是在之前我们使用了一个断言assert(size == 11)必须要让size == 11,这显然是不对的,那么程序在运行过程中就在assert就崩溃了,那么也不会调用delete[] array释放内存,造成内存泄漏。当然这是一个很简单的程序,所以这种问题也比较容易发现,但是当一个复杂的项目有几万行甚至更多行数时,我们往往会忽略这种问题,从而引发严重的内存泄漏。所以我们这样修改下述代码

#include <iostream>
#include <string>
#include <assert.h>
#include <memory>


void test()
{
    double size = 10;
    double* array = new double[10];

    for (int i = 0; i < size; ++i)
    {
        array[i] = 0.0;
    }

    std::unique_ptr<double> p(array);

    assert(size == 11);

}


int main()
{
    test();

    return 0;
}

使用一个std::unique_ptr对裸指针array进行包装,这样裸指针的声明周期就由智能指针来控制,就可以避免无法释放内存的问题。