程序员成长之旅——智能指针
智能指针的发展旅程
我所了解的智能指针其实是为了解决一些异常安全的问题的,它是运用RAII思想封装的类,从而可以达到自动析构的形式。
auto_ptr
C++98提出来的第一个智能指针,它是一个类似管理权转让的形式,拷贝构造或者赋值的时候,将其原本的管理权转交给新的对象。
unique_ptr
C++11提出来的,它的特性非常的明确,就是防拷贝和赋值,将其私有化,或者delete。
shared_ptr
C++11提出来的,这是为了可以支持拷贝和赋值出现的,它的底层其实是用引用计数来实现的。
weak_ptr
这边这个C++11提出来的主要是为了弥补shared_ptr的缺点,有循环引用的可能,因此是用它来解决的。
智能指针的模拟实现
auto_ptr
template<class T>
class auto_ptr
{
public:
auto_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
auto_ptr(const auto_ptr<T>& tmp)
{
_ptr = tmp._ptr;
tmp._ptr = nullptr;
}
auto_ptr<T>& operator=(const auto_ptr<T>& tmp)
{
if (_ptr != tmp._ptr)
{
if(_ptr)
delete(_ptr);
_ptr = tmp._ptr;
tmp._ptr = nullptr;
}
return *this;
}
~auto_ptr()
{
if (_ptr)
delete(_ptr);
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};
从上面我们可以看到,运用auto_ptr的话,在将管理权转让出去之后,如果我们在使用原对象,就会出现错误,为了避免这种错误,C++11提出了更优的智能指针。
unique_ptr
template<class T>
class unique_ptr
{
public:
unique_ptr(T* ptr = nullptr)
:_ptr(ptr)
{}
~unique_ptr()
{
if (_ptr)
delete(_ptr);
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
//C++11的用法
unique_ptr(const unique_ptr<T>&) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
private:
T* _ptr;
};
shared_ptr
template<class T>
class shared_ptr
{
public:
shared_ptr(T* ptr = nullptr)
:_ptr(ptr)
,_pCount(new int(1))
,_mtu(new mutex)
{}
shared_ptr(const shared_ptr<T>& tmp)
:_ptr(tmp._ptr)
,_pCount(tmp._pCount)
,_mtu(tmp._mtu)
{
AddRef();
}
shared_ptr<T>& operator=(const shared_ptr<T>& tmp)
{
if (_ptr != tmp._ptr)
{
Release();
_ptr = tmp._ptr;
_pCount = tmp._pCount;
AddRef();
}
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
~shared_ptr()
{
Release();
}
private:
void AddRef()
{
_mtu->lock();
++(*_pCount);
_mtu->unlock();
}
void Release()
{
bool flag = false;
_mtu->lock();
if (--(*_pCount) == 0)
{
delete(_pCount);
delete(_ptr);
flag = true;
}
_mtu->unlock();
if (flag == true)
delete(_mtu);
}
T* _ptr;
int* _pCount;
mutex* _mtu;
};
以上就是shared_ptr的简单模拟实现,它所存在的最大问题就是,它会有循环引用的问题。举个例子:
typedef struct ListNode
{
shared_ptr<ListNode> _next;
shared_ptr<ListNode> _prev;
int _val;
}ListNode;
void Test()
{
shared_ptr<ListNode> t1(new ListNode);
shared_ptr<ListNode> t2(new ListNode);
t1->_next = t2;
t2->_prev = t1;
}
画个图就理解为何出现循环引用了
而为了解决这个情况,我们就引入了弱指针,来解决这个问题。
weak_ptr
template<class T>
class weak_ptr
{
public:
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& tmp)
{
_ptr = tmp.GetPtr();
}
weak_ptr<T>& operator=(const shared_ptr<T>& tmp)
{
_ptr = tmp.GetPtr();
return *this;
}
private:
T* _ptr;
};
struct ListNode
{
//我们将会发生循环引用的指针用弱指针来完成它
weak_ptr<ListNode> _next;
weak_ptr<ListNode> _prev;
int _val;
};
void Test()
{
shared_ptr<ListNode> t1(new ListNode);
shared_ptr<ListNode> t2(new ListNode);
t1->_next = t2;
t2->_prev = t1;
}
简单的来说,weak_ptr就是只要能构成循环引用的,我们都不将它进行计数加加,这样的话就能有效解除掉循环引用的问题了。
智能指针常见的问题个人理解
1.模拟实现shared_ptr的时候,引用计数是放在栈上还是堆上,说其中原因?
是在堆上的
我认为不放在栈上的理由很简单,就是为了手控这个释放内存的时刻,不会让编译器自动控制,这样子的话,就可能会造成,多个对象指向同一个控制块,当某一个对象作用域 结束的时候,栈上面的这个引用计数就会释放,其它的在使用就会出错。
2.模拟shared_ptr的时候为何在计数++和–的时候要加锁?
会有线程安全的问题
因为++和–是非原子操作的,因此会出现多个线程争抢访问,从而出现线程安全的问题,因此要将其加锁,保证其原子性操作。
补充一个小知识点:+ +为何不是线程安全的
拿++来说的话,它的内部执行至少有三个指令
从内核读取数据
将数据读取到寄存器+1
将寄存器数据写入到内核
并且这三个指令是独立运行的,它们单方面都是原子操作的,但是在多线程并发的情况下,如果两个操作及两个操作以上是一体的话,就是非原子操作,也就是引发线程安全问题的真正缘由。
以上有任何问题,欢迎指正