一 、为什么需要智能指针
C++ 中对象既可以保存在栈上也可以保存堆上。但是某些情况下对象不适合也不应该存储在栈上。
比如:
① 对象需要占用非常大的内存时;比如音频或者视频的帧数据
② 对象的大小在编译时不能确定时; 比如多态
栈对象资源的生命周期通常和函数栈帧的周期保持一致,对于非内置类型而言,编译器会在合适的地方插入构造和析构函数,在出栈的时候自动调用析构函数释放资源。
对于堆上的资源在申请后需要手动释放, 当我们不再需要堆上的资源时就要确保该资源被释放掉,否则会造成资源泄漏。可能是在手动释放之前出现了异常,也可能是由于某些逻辑导致函数提前出栈。
在这种需求下RAII资源管理方式就出现了。RAII 依托栈和析构函数来对所有的资源进行管理,比如:内存,线程同步锁,文件资源, 系统信号量等。
//对内存资源的RAII管理举例
class MemWrapper
{
public:
MemWrapper(BYTE* data = nullptr)
:m_data(data)
{
}
~MemWrapper()
{
delete m_data;
}
BYTE* get() const { return m_data; }
private:
BYTE* m_data;
}
void func()
{
MemWrapper ptr_data( new BYTE);
//do something
*************************
//最后利用ptr_data的析构函数自动释放资源
return;
}
C++11中的智能指针就是RAII方式的延伸。
二 、unique_ptr的简单实现
结合上面的铺垫,直接来代码吧
namespace tool
{
template <class T>
class unique_ptr
{
public:
//explicit 显示声明, 静止编译器自动做类型转换
explicit unique_ptr(T* ptr = nullptr)
:m_ptr(ptr)
{
}
~unique_ptr()
{
delete m_ptr;
}
//定义移动构造而不显示声明拷贝构造, 表示资源只能被移动不能被拷贝
unique_ptr(unique_ptr<T>&& another)
{
m_ptr = another.release();
}
//赋值运算符传值, 因为拷贝构造已经被禁用了,所以赋值运算符只能传右值
unique_ptr& operator= (unique_ptr another)
{
//不用 if(*this == another)
//分为拷贝构造和交换两步,即便拷贝构造里面发生异常, this也是安全完整
unique_ptr(another).Swap(*this);
return *this;
}
T* Release()
{
T* ptr = m_ptr;
m_ptr = nullptr;
return ptr;
}
void Swap(unique_ptr& another)
{
using std::swap;
swap(m_ptr, another.m_ptr);
}
T* Get()const { return m_ptr; }
T& operator* () const { return *m_ptr; }
T* operator->() const { return m_ptr; }
operator bool() const { return m_ptr; }
private:
T* m_ptr;
};
}
//测试用例如下
tool::unique_ptr<int> ptr(new int);
tool::unique_ptr<int> ptr2(ptr); // error; 因为没有拷贝构造
tool::unique_ptr<int> ptr3;
ptr3 = ptr; // error; 赋值只能传右值
ptr3 = std::move(ptr); //ok
三 、shared_ptr的简单实现
unique_ptr算是一个比较完整的智能指针了,特点是一个资源对象只能被一个智能指针对象拥有。 在多线程的情况下通常会需要一个资源对象被多个智能指针拥有的情况,这个时候就需要用到 shared_ptr 了。
多个不同的 shared_ptr 不仅共享一个资源对象而且还需要共享同一个资源计数,当指向对象的最后一个shared_ptr析构时需要删除资源和共享计数。
还是直接上代码吧
//资源计数类
class shared_count
{
public:
shared_count()
:m_count(1){};
~shared_count() = default;
void AddCount()
{
m_count++;
}
long ReduceCount()
{
return --m_count;
}
long GetCount()
{
return m_count;
}
private:
long m_count;
};
// shared_ptr 简易实现
template <typename T>
class shared_ptr
{
public:
explicit shared_ptr(T* ptr = nullptr)
:m_ptr(ptr)
{
if (ptr)
{
m_shared_cnt = new shared_count();
}
}
~shared_ptr()
{
if (m_ptr && !m_shared_cnt->ReduceCount())
{
delete m_ptr;
delete m_shared_cnt;
}
}
//移动
shared_ptr(shared_ptr<T>&& another)
{
m_ptr = another.m_ptr;
if (m_ptr)
{
m_shared_cnt = another.m_shared_cnt;
another.m_shared_cnt = nullptr;
another.m_ptr = nullptr;
}
}
//拷贝构造
shared_ptr(const shared_ptr<T>& another)
{
m_ptr = another.m_ptr;
if (m_ptr)
{
m_shared_cnt = another.m_shared_cnt;
m_shared_cnt->AddCount();
}
}
//赋值,是通过移动还是拷贝赋值由调用者类决定
shared_ptr& operator=(shared_ptr another)
{
another.Swap(*this);
return *this;
}
void Swap(shared_ptr& another)
{
using std::swap;
swap(m_ptr, another.m_ptr);
swap(m_shared_cnt, another.m_shared_cnt);
}
long GetUseCount(){return m_ptr ? m_shared_cnt->GetCount():0;}
T* Get()const { return m_ptr; }
T& operator* () const { return *m_ptr; }
T* operator->() const { return m_ptr; }
operator bool() const { return m_ptr; }
private:
T* m_ptr;
//资源计数要在堆上, 这样才能被共享
shared_count* m_shared_cnt;
};
//测试
tool::shared_ptr<int> ptr(new int);
cout << "ptr count = " << ptr.GetUseCount()<<endl;
tool::shared_ptr<int> ptr1;
ptr1 = ptr;
cout<<"ptr1 count = "<< ptr1.GetUseCount() << "ptr count = "<<ptr.GetUseCount() << endl;
tool::shared_ptr<int> ptr2(move(ptr));
cout << "ptr2 count = " << ptr2.GetUseCount() << "ptr1 count = " << ptr1.GetUseCount() << endl;
if (!ptr)
{
cout<<"ptr is null"<< endl;
}
//输出为:
ptr count = 1
ptr1 count = 2, ptr count = 2
ptr2 count = 2, ptr1 count = 2
ptr is null