IE盒子

搜索
查看: 133|回复: 1

现代C++学习—— 什么是RAII

[复制链接]

1

主题

3

帖子

5

积分

新手上路

Rank: 1

积分
5
发表于 2023-1-23 13:21:07 | 显示全部楼层 |阅读模式
我们将内存简单的分为栈内存和堆内存。
栈上的内存不需要考虑释放问题,而堆内存需要手动释放。一般来讲是以下的流程。
A* p = new A();

// do something

delete p;

  • 获取资源
  • 使用资源
  • 释放资源
通常我们会记得前两步,而忘记第三部,释放资源。
当然可以说,new 一定要和delete配对。
但是在某些情况下,我们会提前跳出,然后忘记匹配一个delete
void foo(int n) {
    A* p = new A();

    if (n > 1) {
        /* ... */
        return;
    }
   
    delete p;
}
当你处理错误,或者一些其它的事情,需要提前结束函数/作用域,这个时候,为了保证资源的释放,你需要每一个分支都加个delete 。
这样是比较麻烦的,所以这里引入RAII。
RAII,全称资源获取即初始化
RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄漏问题。
emmmm,简单来说就是,无论怎么样,结束函数都会调用析构函数,所以我们在析构函数里面写delete。
也就是让编译器在结束函数的每一个分支上,加上析构函数,而我们在析构函数里面delete。
简单的写一下就是这个样子
struct Raii{
    A* p;
    Raii(A* _p) : p{ _p } {};
    ~Raii() {
        delete p;
    }
};

void foo() {
    Raii ptr(new A());
}
这样析构函数会帮我们自动释放的。
ok,但是我们必然不会为了每一个类都去写一个RAII的管理类,所以用模板来生成吧。
template<typename T>
class RAII {
private:
    T* data;
public:
    RAII() : data(nullptr) {}
    explicit RAII(T* rhs) : data{ rhs } {};
    ~RAII() {
        delete data;
    }
    T* operator ->()const {
        return data;
    }
};
这里重载了 -> 来方便我们调用
struct A {
    int x, y, z;
    ~A() {
        cout << "~A()" << endl;
    }
};
这样使用
RAII<A> ptr(new A{ 114,514,1919810 });
cout << ptr->x << endl;
<hr/>如果只是为了不要忘记释放资源,上面就够了,但是这里我们需要引入所有权的概念
如果你对所有权没有概念,推荐阅读 严格鸽:现代C++学习 —— 为什么需要std::move
其实下面的内容就和unique_ptr 一样了。
首先,我们这种写法,是独占所有权的。
RAII<A> p1(new A{ 114,514,1919810 });
RAII<A> p2 = p1;
上面这种情况,可以理解为,p1,p2同时有了 A 的所有权。


当我们析构函数释放的时候,显然这个内存,会被我们释放两次。
所以,我们需要用move来移交所有权


在加上一些东西吧
template<typename T>
class RAII {
private:
    T* data;
public:
    RAII() : data(nullptr) {}
    explicit RAII(T* rhs) : data{ rhs } {};

    ~RAII() {
        if(data)delete data;
    }
    T* operator ->()const {
        return data;
    }
    RAII(const RAII<T>&) = delete;
    RAII(RAII<T>&& rhs) {
        data = rhs.data;
        rhs.data = nullptr;
    }
    RAII& operator = (const RAII&) = delete;

    void operator = (RAII<T>&& rhs) {
        data = rhs.data;
        rhs.data = nullptr;
    }
};
这里,我们把有关拷贝的函数都删除了。


但是我们可以通过move来移交所有权。


这样的代码
RAII<A> p1(new A{ 114,514,1919810 });
cout << "here" << endl;
输出
here
~A()
在析构函数在作用域的最后
而这样的代码
RAII<A> p1(new A{ 114,514,1919810 });
{

    RAII<A>p3 = move(p1);
}
cout << "here" << endl;
输出
~A()
here
我们利用move,把所有权交给了{}里面的一个RAII类,缩窄了其生命周期。
好,那么问题又来了,如何把RAII类作为函数的参数?
这里有个回答就很好
https://www.zhihu.com/question/534389744/answer/2505999713


看情况使用第一条和第三条



回复

使用道具 举报

0

主题

6

帖子

7

积分

新手上路

Rank: 1

积分
7
发表于 2025-6-22 04:47:03 | 显示全部楼层
没人回帖。。。我来个吧
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表