RAII(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种利用对象生命周期来控制资源的技术。
简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。
这种技术是C++为了控制资源,避免内存泄漏的惯用法。
// 使用RAII思想设计的SmartPtr类
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
: _ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
private:
T* _ptr;
};
因为C++并没有垃圾回收机制,要是忘记回收垃圾,就会造成内存泄漏。所以,RAII思想的提出,便解决了这个问题。
RAII思想设计实例:
智能指针:
起初在C++标准库里面是没有智能指针的,最早的智能指针在Boost库里面,直到C++11才加入了shared_ptr和unique_ptr以及weak_ptr。
特点:1.RAII思想 2.具有指针行为
版本1: std:::auto_ptr
//RAII + *和->的重载
template<class T>
class SmartPoint{
public:
SmartPoint(T* ptr=nullptr):_ptr(ptr)
{}
~SmartPoint()
{
std::cout<<"delete"<<_ptr<<std::endl;
delete _ptr;
}
T& operator*(){return *_ptr};
T* operator->(){return _ptr};
private:
T* _ptr;
};
缺陷:管理权转移会导致原对象指针置空,无法正常使用
详情如下,关于auto_ptr拷贝构造和赋值重载的实现:
template<class T>
class AutoPtr()
{
//p对象资源转移后,自身置空,与资源断开联系
AutoPtr(AutoPtr<T>& p):_ptr(p._ptr)
{
p._ptr = NULL;
}
AutoPtr<T>& operator=(AutoPtr<T>& p)
{
//检测是否是给自己赋值
if(this != &p)
{
//释放当前资源
if(_ptr)
delete _ptr;
//将p资源转移给本对象,p的指针置空
_ ptr = p._ptr;
p._ptr = NULL;
}
}
private: T* _ptr;
};
版本2: std::unique_ptr ---防拷贝
template<T>
class UniPtr()
{
//简单粗暴防拷贝
//C++11防拷贝: delete
UniPtr(UniPtr<T> const&) = delete;
UniPtr<T>& operator=(Unique<T> const&) = delete;
private:
T* _ptr;
};
版本3: std::shared_ptr ---引用计数
原理:在拷贝构造时,使用同一份计数
(1)每个shared_ptr对象内,都有一份资源被多少个对象共享的计数
(2)对象销毁时,计数-1
如果计数==0,没有其他对象共享,释放资源
如果计数>0,有其他对象共享,不释放资源
#include<thread>
#include<mutex>
template <class T>
class SharedPtr
{
public:
//构造
SharedPtr(T* ptr = nullptr)
: _ptr(ptr)
, _pRefCount(new int(1))
, _pMutex(new mutex)
{
// 如果是一个空指针对象,则引用计数给0
if (_ptr == nullptr)
*_pRefCount = 0;
}
//析构
~SharedPtr() {Release();}
//拷贝构造
SharedPtr(const SharedPtr<T>& sp)
: _ptr(sp._ptr)
, _pRefCount(sp._pRefCount)
, _pMutex(sp._pMutex)
{
// 如果是一个空指针对象,则不加引用计数,否则才加引用计数
if (_ptr)
AddRefCount();
}
// sp1 = sp2
SharedPtr<T>& operator=(const SharedPtr<T>& sp)
{
//if (this != &sp)等同于下面
if (_ptr != sp._ptr)
{
// 释放管理的旧资源
Release();
// 共享管理新对象的资源,并增加引用计数
_ptr = sp._ptr;
_pRefCount = sp._pRefCount;
_pMutex = sp._pMutex;
if (_ptr){
AddRefCount();
}
return *this;
}
//指针操作
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
int UseCount() {return *_pRefCount;}
T* Get() { return _ptr; }
//原子计数: ++操作
int AddRefCount()
{
// 加锁或者使用加1的原子操作
_pMutex->lock();
++(*_pRefCount);
_pMutex->unlock();
return *_pRefCount;
}
//原子计数: --操作
int SubRefCount()
{
// 加锁或者使用减1的原子操作
_pMutex->lock();
--(*_pRefCount);
_pMutex->unlock();
return *_pRefCount;
}
private:
//--与判断释放操作
void Release()
{
// 引用计数减1,如果减到0,则释放资源
if (_ptr && SubRefCount() == 0)
{
delete _ptr;
delete _pRefCount;
}
}
private:
int* _pRefCount; // 引用计数,用指针让所有对象共享该值
T* _ptr; // 指向管理资源的指针
mutex* _pMutex; // 互斥锁
};
关于shared_ptr的两个问题:
线程安全问题: 不是100%安全
场景: 两个线程同时读写同一个shared_ptr
1.引用计数是安全且无锁的
2.对象读写不安全,因为有两个数据成员,不能原子化操作改进:代码上加锁控制
实例:
循环引用问题:
场景: 两个对象中的shared_ptr互相指向对方,导致两者计数变为2,pa,pb销毁之后,资源计数变为1,此时,即A内部有指向B,B内部有指向A,这样对于A,B必定是在A析构后B才析构,对于B,A必定是在B析构后才析构A,这就是循环引用问题,违反常规,导致内存泄露。
解决: 使用weak_ptr替代相互引用的shared_ptr
weak_ptr: weak_ptr是弱共享指针,其实就是share_ptr的辅助指针,不具备指针的功能。主要是为了协助 shared_ptr 工作,可以观测资源的使用情况。weak_ptr 只对 shared_ptr 进行引用,不会改变引用计数,当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效。
主要功能:1.不会改变引用计数,可以观测资源使用情况,生命周期和shared_ptr一样
2.可以使用lock来借用shared_ptr来完成操作
非new对象问题:
如果管理的是new出来的多个对象,怎么删除对象呢,shared_ptr提供了删除器
// 仿函数的删除器 --- 在创建智能指针的时候,传参释放方式
template<class T>
struct FreeFunc {
void operator()(T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
template<class T>
struct DeleteArrayFunc {
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
int main()
{
FreeFunc<int> freeFunc;
shared_ptr<int> sp1((int*)malloc(4), freeFunc); //第二个参数为删除器选择
DeleteArrayFunc<int> deleteArrayFunc;
shared_ptr<int> sp2((int*)malloc(4), deleteArrayFunc);
return 0;
}
原文链接: https://www.cnblogs.com/Duikerdd/p/12030949.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/191701
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!