智能指针是为了防止因为诸如抛异常导致进程异常退出而导致的内存泄露问题,当然也不排除自身忘记释放内存的可能。
具体例子:
#include <iostream>
void Merge()
{
// 1.内存申请了忘记释放
int* p1 = (int*)malloc(sizeof(int));
int* p2 = new int;
// 2.异常安全问题
int* p3 = new int[10];
Func(); // 这里假设Func函数抛异常导致 delete[] p3未执行,p3没被释放.
delete[] p3;
}
void Func()
{
throw std::bad_typeid() ;
}
智能指针的原理及使用
RAII思想(Resource Acquisition Is Initialization)资源获取即初始化,是利用对象生命周期控制资源的一种技术。在对象构造时获取资源,在对象析构时释放资源,这样我们将资源的管理就转移给一个对象。
重载operator*和operator->,使得该类像指针一样使用。
好处:
1、不需要显式地释放资源。
2、采用这种方式,对象所获取的资源在其生命周期内始终有效。
四种智能指针
std::auto_ptr:最早的一种智能指针,由于在拷贝构造和赋值重载时,会造成原指针的悬空问题,本质就是管理权限的转移,所以被诟病,也不推荐使用。
具体模拟实现:
namespace Smart_ptr
{
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr)
:_ptr(ptr)
{}
~auto_ptr() {
if (_ptr) delete _ptr;
}
auto_ptr(const auto_ptr<T>& ptr)
{
_ptr = ptr._ptr;
delete ptr._ptr;
ptr._ptr = nullptr;
}
auto_ptr<T>& operator=(const auto_ptr<T>& ptr)
{
if (&ptr != this)
{
if (_ptr) delete _ptr;
_ptr = ptr._ptr;
delete ptr._ptr;
ptr._ptr = nullptr;
}
return *this
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr = nullptr;
};
}
std::unique_ptr:禁止拷贝构造和赋值重载,每个资源只能有一份。
具体模拟实现:
#include <iostream>
namespace Smart_ptr
{
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr) delete _ptr;
_ptr = nullptr;
}
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr& operator=(const unique_ptr<T>&) = delete;
T* operator->()
{
return *_ptr;
}
T& operator*()
{
return _ptr;
}
private:
T* _ptr = nullptr;
};
}
std::shared_ptr:通过给每一份资源一个唯一的引用计数,实现多个shared_ptr对象之间间的资源共享。
原理
1、shared_ptr在内部给每一份资源都维护了一份引用计数,用来记录该资源被几个对象所共享。
2、在对象被销毁时,该对象内部的引用计数减一。
3、如果引用计数减一后为0,说明此时的对象是最后一个使用该资源的,该资源必须被释放。
4、如果引用计数减一后不是0,那么就不能释放该资源,否则其他对象所管理的资源的地址就是野指针了。
另外,在模拟实现时,要给shared_ptr的引用计数加一把锁。因为shared_ptr引用计数的P操作(减减操作)和V操作(加加操作)是线程安全的,但注意,shared_ptr指向的资源不是线程安全的。智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题。
shared_ptr在特殊情况下会存在循环引用问题
class n1
{
public:
std::shared_ptr<int> p_next;
std::shared_ptr<int> p_prev;
};
class n2
{
public:
std::shared_ptr<int> p_next;
std::shared_ptr<int> p_prev;
};
其中_next 和 _prev都是shared_ptr。那么在创建n1和n2的时候。两者类内的引用计数都是1,此时让n1的_next指向n2,那么n2的计数就是2
使n2的_prev指向n1,那么n1的计数变为n2,在析构的时候,后定义的先析构,此时n2先析构,n2的计数变为1,n1后析构,n1的计数变为1,那么_next和_prev何时析构?
因为_next指向n2,n2的计数为1,n2的_prev指向n1,n1的计数为1,想让_next析构,就得使_next所在的n1计数变为0,所以要让指向n1的_prev先析构,
而_prev析构的条件是要先让_prev所在的n2计数变为0,所以要让指向n2的_next先析构
综上就形成了一个死循环。
具体模拟实现:
#include <iostream>
#include <mutex>
namespace Smart_ptr
{
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr)
{
_ptr = ptr;
_count = new int(1);
_mtx = new std::mutex;
}
shared_ptr(const shared_ptr<T>& ptr)
{
_ptr = ptr._ptr;
_count = ptr._count;
_mtx = ptr._mtx;
_mtx.lock();
(*_count)++;//引用计数加一
_mtx.unlock();
}
shared_ptr& operator=(const shared_ptr<T>& ptr)
{
if (_ptr != ptr._ptr)
{
//释放管理的旧资源
bool delete_flag = false;
_mtx.lock();
if (--_count == 0)
{
delete _ptr;
_ptr = nullptr;
delete_flag = true;
}
_mtx.unlock();
if (delete_flag) delete _mtx;
//共享新对象的资源,并增加引用计数
_ptr = ptr._ptr;
_count = ptr._count;
_mtx = ptr._mtx;
_mtx.lock();
_count++;
_mtx.unlock();
}
return *this;
}
T* operator->()
{
return _ptr;
}
T& operator*()
{
return *_ptr;
}
int UseCount()
{
return *_pRefCount;
}
T* Get()
{
return _ptr;
}
private:
int* _count;
T* _ptr;
std::mutex _mtx;
};
}
std::weak_ptr:weak_ptr的提出,是为了解决shared_ptr的循环引用问题,weak_ptr可以接收shared_ptr且不会增加该对象的引用计数。
具体实现:
#include <iostream>
namespace Smart_ptr
{
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(shared_ptr<T>& sp)
:_ptr(sp.Get())
{}
weak_ptr(weak_ptr<T>& wp)
:_ptr(wp.get())
{}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
T* get()
{
return _ptr;
}
private:
T* _ptr;
};
}