RAII技术学习

“Resource Acquisition is Initialization”
资源获取即初始

一,什么是RAII

RAII中的R代表资源,计算机中的一切可由程序员管理的,如指针的申请的内存,开启占用的文件描述符,创建与销毁的锁等等
对于资源的一般操作即就是

  • 申请
  • 使用
  • 释放

为了避免一些人为与非人为的因素造成资源的未释放或者是未初始化的野指针的问题,如没有delte的内存,没有unlock的锁,所诞生出的技术

一些常见情况

一.人为忘记释放

std::mutex m;void fn() 
{
    m.lock();                 
   //read
    使用资源
    //wrire
    m.unlock();                
}

如果我们忘记释放锁,或者wirte的时候出现err,或者因为中间庞大的逻辑与架构导致对解锁的管理难以掌控,那么别的线程再也无法加锁成功,而且还会导致一直阻塞

二 . 在C++中,经常动态分配内存,我们都知道new和delete一定要匹配使用。
但某些情况,比如异常被抛出时,程序的正确流程被改变,仍可能导致内存溢出。例如:

特殊情况


int main()
{
	try
	{
		funcOne();
	}
	catch (const std::exception& e)
	{
		cerr << "Exception caught!" << endl;
	}
    return 0;
}
 
void funcOne()
{
	string str1;
	string* str2 = new string();
	funcTwo();
	delete str2;
}
 
void funcTwo()
{
	ofstrean ofs;
	ofs.open("filename");
	throw exception();
	ofs.close();
}

当functwo的函数抛出异常时,根据栈展开,最近的处理程序在main,那么程序就会跳回main,ofs.close()将没有执行机会,资源就会来不及释放,当然也可以每跳一层释放一层但是会显得逻辑臃肿

为了解决这样的问题

由于系统的资源不具有自动释放的功能,而C++中的类具有自动调用析构函数的功能。如果把资源用类进行封装起来,对资源操作都封装在类的内部,在析构函数中进行释放资源。当定义的局部变量的生命结束时,它的析构函数就会自动的被调用,如此,就不用程序员显示的去调用释放资源的操作了。

二,经典实现

template <typename T>
class lock_guard
{
private:
    T _mutex;
public:
    explicit lock_guard(T &mutex) : _mutex(mutex)
    {
        _mutex.lock();
    }
    ~lock_guard()
    {
        _mutex.unlock();
    }
};

像下面这种情况 这把锁就会被初始化时加上,在栈对象被销毁时解锁

std::mutex mut;
int write_to_a_file_descriptor(std::string content) 
{
 
    std::lock_guard<std::mutex> lock(mut);
    // critical area below (might throw exception)
 
    // Writing  content to a file descriptor...
 
    // Critical areas above
}

智能指针

对于内存的管理一直是让c/c++程序员头疼的事情,内存泄漏(即 malloc() 内存在对应的 free() 调用执行后永不被释放),对于未初始化的野指针的错误使用,或者错误指向造成的问题,但是我们可以把内存的释放时机交给操作系统来决定

// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
    public:
        SmartPtr(T* ptr = nullptr)
        : _ptr(ptr)
        {}
        ~SmartPtr()
        {
        if(_ptr)
        delete _ptr;
    }
    private:
        T* _ptr;
};

C++为我们提供了四种智能指针但常用只有三种另外一个已经被废除

auto_ptr

C++98提供了auto_ptr的智能指针
auto_ptr的实现原理:管理权转移的思想,即当拷贝和赋值时将智能指针管理的内存地址进行转移,也就是一份空间内存只有一个智能指针进行管理
被废弃原因:当对象拷贝或者赋值后,前面的对象就悬空了

1.unique_ptr
它持有对对象的独有权——两个unique_ptr 不能指向一个对象,即 unique_ptr 不共享它所管理的对象。它无法复制到其他 unique_ptr,无法通过值传递, 只能通过移动转移
在这里插入图片描述

//unique_ptr:不存在拷贝和赋值-没有管理权的转移
#include <iostream>
#include <string.h>
#include <utility>
#include <vector>
template <typename T>
class unqie_ptr
{

public:
    constexpr unqie_ptr() = default;
    explicit unqie_ptr(T *ptr)
        : _ptr(ptr)
    {
    }
    unqie_ptr(unqie_ptr &rhs) = delete;
    unqie_ptr(unqie_ptr &&rhs) : _ptr(rhs.realse()){};

    unqie_ptr &operator=(std::nullptr_t)
    {
        realse();
        return *this;
    }
    unqie_ptr &operator=(const unqie_ptr &rhs) = delete;
    unqie_ptr &operator=(unqie_ptr &&rhs)
    {
        reset(rhs.realse());
        return *this;
    }
    explicit operator bool() { return static_cast<bool>(_ptr); };
    T *get() const noexcept //得到落指针;
    {
        return _ptr;
    }
    T *realse() //释放权限
    {
        return std::exchange(_ptr, nullptr);
    }
    void reset(T *ptr)
    {
        delete std::exchange(_ptr, ptr);
    }
    void swap(unqie_ptr &rhs)
    {
        std::swap(_ptr, rhs._ptr);
    }
    ~unqie_ptr()
    {
        delete _ptr;
    }

    T &operator*() //返回解引用的
    {
        return *_ptr;
    }
    T *operator->()
    {
        return _ptr; //返回裸指针
    };

private:
    T *_ptr{nullptr};
};


2.shared_ptr
shared_ptr采用的是引用计数原理来实现多个shared_ptr对象之间共享资源:

shared_ptr在内部会维护着一份引用计数,用来记录该份资源被几个对象共享。
当一个shared_ptr对象被销毁时(调用析构函数),析构函数内就会将该计数减1。
如果引用计数减为0后,则表示自己是最后一个使用该资源的shared_ptr对象,必须释放资源。
如果引用计数不是0,就说明自己还有其他对象在使用,则不能释放该资源,否则其他对象就成为野指针。
引用计数是用来记录资源对象中有多少个指针指向该资源对象。
在这里插入图片描述

#include <iostream>
#include <string>
#include <atomic>
#include<vector>
class refcout
{
public:
    int use_count() { return _count; }
    void inc_count() { ++_count; }
    int dec_count() { --_count; }

private:
    std::atomic_int _count{1};
};

template <class T>
class sharedptr
{
public:
    sharedptr() = default;
    explicit sharedptr(T *ptr) : _ptr(ptr)
    {
        if (ptr)
        {
            _rep = new refcout{};
        }
    }
    sharedptr(const sharedptr &rhs) : _ptr(rhs._ptr), _rep(rhs._rep)
    {
        if (_rep)
        {
            _rep->inc_count();
        }
    }
    sharedptr(sharedptr &&rhs) : _ptr(rhs._ptr), _rep(rhs._rep)
    {
        rhs._ptr = nullptr;
        rhs._rep = nullptr;
    }
    ~sharedptr()
    {
        if (_rep && _rep->dec_count())
        {
            delete _ptr;
            delete _rep;
        }
    }
    sharedptr &operator=(const sharedptr &rhs)
    {
        if (&this != rhs)
            sharedptr{rhs}.swap(*this);
        return *this;
    }
    sharedptr &operator=(sharedptr &&rhs)
    {
        if (&this != rhs)
            sharedptr{move(rhs)}.swap(*this);
        return *this;
    }

    void swap(sharedptr &rhs)
    {
        std::swap(_ptr, rhs._ptr);
        std::swap(_rep, rhs._rep);
    }
    void reset(T *ptr = nullptr)
    {
        sharedptr{ptr}.swap(*this);
    }
    T *get()
    {
        return _ptr;
    }

    int us_count()
    {
        return _rep ? _rep->use_count() : 0;
    }

    T &operator*()
    {
        return *_ptr;
    }
    T *operator->()
    {
        return _ptr;
    }
    operator bool()
    {
        return static_cast<bool>(_ptr);
    }

private:
    T *_ptr{nullptr};
    refcout *_rep{nullptr};
};

3.weak_ptr

一般shared_ptr可以满足资源管理的大部分情况,但是也有些情况是shared_ptr不能处理的,这时候就需要使用weak_ptr

struct tNode
{
	int _data;
	shared_ptr<Node> _prev;
	shared_ptr<Node> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	shared_ptr<Node> node1(new ListNode);
	shared_ptr<Node> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

weak_ptr类的对象它可以指向shared_ptr,并且不会改变shared_ptr的引用计数。一旦最后一个shared_ptr被销毁时,对象就会被释放。

struct tNode
{
	int _data;
	weak_ptr<Node> _prev;
	weak_ptr<Node> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{
	shared_ptr<Node> node1(new ListNode);
	shared_ptr<Node> node2(new ListNode);
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	node1->_next = node2;
	node2->_prev = node1;
	cout << node1.use_count() << endl;
	cout << node2.use_count() << endl;
	return 0;
}

weak_ptr对象指向shared_ptr对象时,不会增加shared_ptr中的引用计数,因此当node1销毁掉时,则node1指向的空间就会被销毁掉,node2类似,所以weak_ptr指针可以很好解决循环引用的问题。

RAII的特点一般即为,利用构造函数申请资源,利用析构函数释放资源,所以可以不止于此,对于一些其他的资源如文件描述符,都可以利用类对象的特性实现管理

一些特殊实现

5 struct Timer {
  6   std::chrono::time_point<std::chrono::system_clock> start, end;
  7   std::chrono::duration<float> duration;
  8 
  9   Timer() {
 10     start = std::chrono::high_resolution_clock::now();
 11   }
 12 
 13   ~Timer() {
 14     end = std::chrono::high_resolution_clock::now();
 15     duration = end - start;
 16 
 17     float ms = duration.count() * 1000.0f;
 18     std::cout << "Timer took " << ms << "ms " << std::endl;
 19   }
 20 };                                                                                                                                        
 21 
 22 void Function() {
 23   Timer timer;// 为整个作用域计时
 24 
 25   for(int i = 0; i < 100; i++){
 26     std::cout << "hello\n";
 27   }
 28 }
 29 
 30 int main(int argc, char **argv) {
 31   Function();
 32 
 33   std::cin.get();
 34 }
~                    

注:

要做到资源的最精细化管理,还是要自己学会自己动手,因为RaII也无法做到最精细的管理,对比其他类似GO语言等的Gc机制,完全把回收的工作交给操作系统巡回使得解放程序员,但会造成调用时的性能波动开销,也会导致一定的暂停响应状态( 感兴趣的可以下去查一查),因此最精确的内存控制还得靠你的细心思考与设计;

  • 6
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值