一、shared_ptr的弊端
struct Listnode
{
int _val;
std::shared_ptr<Listnode> _prev;
std::shared_ptr<Listnode> _next;
Listnode(int val )
:_val(val)
,_prev(nullptr)
,_next(nullptr)
{}
~Listnode()
{
cout << "~Listnode()" << endl;
}
};
int main()
{
std::shared_ptr<Listnode> n1(new Listnode(20));
std::shared_ptr<Listnode> n2(new Listnode(10));
n1->_next = n2;
n2->_prev = n1;
return 0;
}
如上的情况就出现了循环引用,两个对象相互掣肘。
1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
4. 也就是说_next析构了,node2就释放了。
5. 也就是说_prev析构了,node1就释放了。
6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。
展开来讲:
两个对象的引用计数都变为2,当要进行析构时就陷入了循环引用。
1、左边节点要想析构就得等右边的_prev去调用析构。
2、右边节点被delete时去调用_prev.
3、而右边节点被析构需要左边节点的_next去调用析构
4、左边节点被delete时去调用_prev
5、左边节点要想析构就得等右边的_prev去调用析构。
到这里就以及死循环了。
二、weak
它的特点就是不增加引用计数,专门用于shared_ptr出现循环引用的情况。
struct Listnode
{
int _val;
std::weak_ptr<Listnode> _prev;
std::weak_ptr<Listnode> _next;
Listnode(int val )
:_val(val)
{}
~Listnode()
{
cout << "~Listnode()" << endl;
}
};
int main()
{
std::shared_ptr<Listnode> n1(new Listnode(20));
std::shared_ptr<Listnode> n2(new Listnode(10));
n1->_next = n2;
n2->_prev = n1;
return 0;
}
2.1模拟实现
struct Listnode
{
int _val;
gaz::weak_ptr<Listnode> _prev;
gaz::weak_ptr<Listnode> _next;
Listnode(int val )
:_val(val)
{}
~Listnode()
{
cout << "~Listnode()" << endl;
}
};
int main()
{
gaz::shared_ptr<Listnode> n1(new Listnode(20));
gaz::shared_ptr<Listnode> n2(new Listnode(10));
n1->_next = n2;
n2->_prev = n1;
return 0;
}
template<class T>
class weak_ptr
{
public:
//RALL
weak_ptr()
:_ptr(nullptr)
{}
weak_ptr(const shared_ptr<T>& sp)
{
_ptr = sp.get();
}
weak_ptr<T>& operator=(const shared_ptr<T>& sp)
{
_ptr = sp.get();
return *this;
}
T& operator*()
{
return *_ptr;
}
T* operator->()
{
return _ptr;
}
private:
T* _ptr;
};