智能指针是为了解决C++里防止程序员因为忘记释放资源而造成内存泄漏的问题
RAII
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。借此, 我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
- 不需要显式地释放资源。
- 采用这种方式,对象所需的资源在其生命期内始终保持有效。
智能指针
一、原理:
- RAII特性
- 重载operator*和opertaor->,具有像指针一样的行为
理解:利用一个行为很像指针的对象帮你保管资源,同时当出了对象作用域时,会自动帮你释放,智能指针不是一个真正的指针:而是一个对象,可以使用操控指针的语句来操控这个对象。并且因为智能指针是栈空间上类的对象。所以当程序运行结束后,会自动调用其析构函数自动释放。这样就不会担心在申请资源和释放资源中程序抛异常了。
二、分类:
| 指针名 | 优点 | 缺点 |
|---|---|---|
| auto_ptr | 实现简单 | 不安全,进行复制后会有指针悬空现象 |
| unique_ptr | 简单粗暴且安全 | 无法拷贝,仅能进行所有权转移 |
| shared_ptr | 安全,并且能拷贝 | 为了解决循环引用问题,必须借助weak_ptr实现完整功能 |
注:C++库中的智能指针都定义在#include <memory>这个头文件中
三、分析:
- auto_ptr
1.实现原理:将资源管理权进行转移,即将一个指针所指向的空间交给另一个指针。
2.缺陷:管理权一直在变,但只允许有一个指针指向该资源,两个指针就无法指向同一块资源。这样的话当调用拷贝构造和赋值运算符重载的时候,将原来所管理的资源转移给当前对象后,就断开了原ptr与其所管理资源的联系,导致再通过原对象访问资源 时就会出现问题。

3.代码实现:
template<class T>
class AutoPtr
{
public:
AutoPtr(T* ptr = nullptr)
:_ptr(ptr)
{}
~AutoPtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
AutoPtr(AutoPtr<T>& ap) //拷贝构造
:_ptr(ap._ptr) //将ap中资源转移到当前对象中
{
ap._ptr = nullptr; //令ap与其所管理资源断开联系
}
AutoPtr<T>& operator=(AutoPtr<T>& ap) //赋值运算符重载
{
if (&this != &ap) //检测是否给自己赋值
{
if (_ptr) //释放当前对象中的资源
delete _ptr;
_ptr = ap._ptr;
ap._ptr = nullptr;
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
- unique_ptr (C++11中提供)
1.实现原理:简单粗暴的防拷贝,即不让拷贝和赋值
2.特点:让自己的东西无法被别人获取
3.代码实现:
template<class T>
class UniquePtr
{
public:
UniquePtr(T* ptr = nullptr)
:_ptr(ptr)
{}
~UniquePtr()
{
if (_ptr)
{
delete _ptr;
_ptr = nullptr;
}
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
//C++98中防拷贝的方式:声明成私有 并且 不进行实现
//UniquePtr(UniquePtr<T> const &);
//UniquePtr& operator=(UniquePtr<T>const &);
//C++11中防拷贝的方式:采用delete来禁止默认创建的函数
UniquePtr(UniquePtr<T>const &) = delete;
UniquePtr& operator=(UniquePtr<T>const &) = delete;
private:
T* _ptr;
};
- shared_ptr(C++11中提供)
1.实现原理:通过引用计数的方式来实现多个shared_ptr对象之间 共享资源
- 在shared_ptr内部,给每个资源都维护着一份计数,用来记录该份资源被几个对象共享。
- 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一。
- 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
- 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。
注:count的类型是int*,且它是由多个对象共有的,在对count++或--时需要加上互斥锁,这样才能保证线程安全。
shared_ptr的线程安全问题:
- 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或--,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2,这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以在智能指针中引用计数++、--是需要加锁的,也就是说引用计数的操作是 线程安全的。
- 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
2.缺点:双向链表中可能会出现循环引用问题,导致资源空间不能完全释放掉。
解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了。
3.代码实现:
#include <thread>
#include <mutex>
template<class T>
class SharedPtr
{
public:
SharedPtr(T* ptr = nullptr)
:_ptr(ptr)
,_pCount(new int(1))
,_pMutex(new mutex)
{
// 如果是一个空指针对象,则引用计数给0
if (_ptr == nullptr)
*_pCount = 0;
}
SharedPtr()
{
Release();
}
SharedPtr(const SharedPtr<T>& sp)
:_ptr(sp._ptr)
, _pCount(sp._pCount)
, _pMutex(sp._pMutex)
{
if (_ptr) //指针对象不为空才可进行加引用计数
AddCount();
}
SharedPtr<T>& operator=(const SharedPtr<T>& sp) //sp1 = sp2;
{
if (_ptr != sp._ptr) //if(this != &sp)
{
Release(); //释放旧资源
//共享管理新对象的资源
_ptr = sp._ptr;
_pCount = sp._pCount;
_pMutex = sp._pMutex;
//并进行加引用计数
if (_ptr)
AddCount();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
int UseCount()
{
return *_pCount;
}
T* Get()
{
return _ptr;
}
int AddCount() //引用计数加1
{
_pMutex->lock();
++(*_pCount);
_pMutex->unlock();
return *_pCount;
}
int SubCount() //引用计数减1
{
_pMutex->lock();
--(*_pCount);
_pMutex->unlock();
return *_pCount;
}
private:
void Release()
{
if (_ptr && SubCount() == 0) //判断引用计数减1后是否为0,为0则释放资源
{
delete _ptr;
delete _pCount;
}
}
private:
T* _ptr; //指向管理资源的指针
int* _pCount; //引用计数
mutex* _pMutex; //互斥锁
};
3361

被折叠的 条评论
为什么被折叠?



