智能指针的优点不多赘述,前面的文章已经说过。这次直接手撕一个智能指针的代码顺便帮助自己理解一下智能指针的使用。
首先,智能指针有几个注意点:
- 智能指针适用于各类型参数,所以要用T模板;
- 智能指针有引用计数,所以在构造和析构的时候要进行引用计数的加减;
- 需要重载指针的“->”“*”功能,使其能像正常指针一样使用;
- 主要是析构函数的不同之处在于加个条件判断
好,那我们开始吧:
class Shared_mptr {
public:
//空类构造,count,ptr均置空
Shared_mptr() :count(0), ptr_((T*)0) {}
//赋值构造,count返回int指针,必须new int,ptr指向值
Shared_mptr(T* p) :count(new int(1)), ptr_(p) {}
//拷贝构造,注意是&引用,此处注意的一点是,count需要+1
Shared_mptr(Shared_mptr<T> &other) :count(&(++ *other.count)), ptr_(other.ptr_) {}
//重载->返回T*类型
T* operator->() { return ptr_; }
//重载*返回T&引用
T& operator*() { return *ptr_; }
//重载=,此处需要将源计数减一,并判断是否需要顺便析构源,然后将thiscount+1,注意最后返回*this
Shared_mptr<T>& operator=(Shared_mptr<T>& other)
{
if (this == &other)
return *this;
++*other.count;
if (this->ptr_&&--*this->count == 0)
{
delete ptr_;
delete count;
cout << "delete from =" << endl;
}
this->count = other.count;
this->ptr_ = other.ptr_;
return *this;
}
//析构,当ptr_存在且在此次析构后count==0,真正析构资源
~Shared_mptr()
{
if (ptr_&&--*count == 0)
{
delete ptr_;
delete count;
cout << "delete from ~" << endl;
}
}
//返回count
int getRef()
{
return *count;
}
private:
int *count;//注意此处是count*,因为计数其实是同一个count,大家都以指针来操作;
T* ptr_;
};
int main()
{
Shared_mptr<int> pstr(new int(2));//注意此处是new int
cout << "pstr:" << pstr.getRef() << " " << *pstr << endl;
Shared_mptr<int> pstr2(pstr);
cout << "pstr:" << pstr.getRef() << " " << *pstr << endl;
cout << "pstr2:" << pstr2.getRef() << " " << *pstr2 << endl;
Shared_mptr<int> pstr3(new int(4));
cout << "pstr3:" << pstr3.getRef() << " " << *pstr3 << endl;
pstr3 = pstr2;
cout << "pstr:" << pstr.getRef() << " " << *pstr << endl;
cout << "pstr2:" << pstr2.getRef() << " " << *pstr2 << endl;
cout << "pstr3:" << pstr3.getRef() << " " << *pstr3 << endl;
return 0;
}
思考一下,智能指针是线程安全的吗?
因为从代码中可以看出,引用计数本身是线程安全且无锁的,所以,在多线程的时候可能就会出现错误。
最典型的就是因为指针复制和引用计数是两步操作,所以会二者中间进行某些操作导致错误。
举个例子:
shared_ptr<Foo> l(new Foo);
shared_ptr<Foo> m;
shared_ptr<Foo> n(new Foo);
开始时,相安无事。
- m=l;执行此操作,m开始复制l,假设复制完ptr_l后,被中断;
- l=n;另一线程开始执行此操作,ptr_m=ptr_n,然后m_count也靠着n的count+1;
- 此时线程回到原m=l;咦,ptr_m指向的ptr_m不见了,没了,导致内存泄漏,指针指向了不存在的对象。(这也是多线程容易出现内存泄漏问题的原因)
如何保证操作的线程安全性,最简单就是在进行使用时加锁。类中的实现我暂时还没看,等我想清楚了再来补充。
嘘:还有个uniqueptr
#include <string>
#include <iostream>
using namespace std;
template <typename T>
class UniquePtr
{
public:
//构造函数
UniquePtr(T* ptr = nullptr) :m_pResource(ptr) {};
//析构函数
~UniquePtr()
{
del();
}
//先删除源对象,而后复制
void reset(T* pResource)
{
del();
m_pResource = pResource;
}
//交给
T* release()
{
T* tmp = m_pResource;
m_pResource = nullptr;
return tmp;
}
T* get()
{
return m_pResource;
}
operator bool() const
{
return m_pResource != nullptr;
}
T* operator->() { return m_pResource; }
T& operator*() { return *m_pResource; }
private:
void del()
{
if (m_pResource == nullptr)
{
return;
}
delete m_pResource;
m_pResource = NULL;
}
UniquePtr(UniquePtr<T> &other) = delete;
UniquePtr& operator=(const UniquePtr &) = delete;
T *m_pResource;
};