智能指针(shared_ptr的实现)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/xiaodu655/article/details/85780240

1.实现原理:shared_ptr是利用一个计数器,无论我们使用拷贝构造函数、赋值运算符重载、作为函数返回值、或作为参数传给一个参数时计数器+1,
当shared_ptr被赋予一个新值或者需要销毁时,计数器–,直到计数器为0时,调用析构函数,释放对象,并销毁其内存。shaerd_ptr不直接支持管理动态数组,如果希望使用shared_ptr管理一个动态数组,必须定制自己的删除器。

class SharedPtr
{
public:
        SharedPtr(T*ptr=NULL)
               :_ptr(ptr)
               , _pcount(new int(1))
        {}
        SharedPtr(const SharedPtr&s)
               :_ptr(s._ptr)
               , _pcount(s._pcount)
        {
               *(_pcount)++;
        }
        SharedPtr<T>&operator=(const SharedPtr&s)
        {
               if (this!= &s)
               {
                       if (--(*(this->_pcount)) == 0)
                       {
                              delete this->_ptr;
                              delete this->_pcount;
                       }
                       _ptr = s._ptr;
                       _pcount = s._pcount;
                       *(_pcount)++;
               }
               return *this;
        }
        T&operator*()
        {
               return *(this->_ptr);
        }
        T*operator->()
        {
               return this->_ptr;
        }
        ~SharedPtr()
        {
               --(*(this->_pcount));
               if (this->_pcount == 0)
               {
                       delete _ptr;
                       _ptr = NULL;
                       delete _pcount;
                       _pcount = NULL;
               }
        }
private:
        T*_ptr;
        int *_pcount;//指向引用计数的指针
};
int main()
{
        SharedPtr<int>p1(new int(1));
        SharedPtr<int>p2(p1);
        SharedPtr<int> p3 = p2;//调用的是拷贝构造函数、因为p3原本是不存在的。
        p3 = p2;
        system("pause");
        return 0;
}

2.但是其存在一些问题:
问题一:循环引用问题:

template<class T>
struct ListNode
{
        ListNode(T value)
        :_value(value)
        {
               cout << "ListNode()" << endl;
        }
        ~ListNode()
        {
               cout << "~ListNode()" << endl;
        }
        T _value;
        shared_ptr<ListNode<T>> _prev;
        shared_ptr<ListNode<T>> _next;
};
void test()
{
        shared_ptr<ListNode<int>> p1(new ListNode<int>(1));
        shared_ptr<ListNode<int>> p2(new ListNode<int>(2));
        cout << p1.use_count() << endl;
        cout << p2.use_count() << endl;
        p1->_next = p2;
        p2->_prev = p1;
        cout << p1.use_count() << endl;
        cout << p2.use_count() << endl;
}
int main()
{
        test();
        system("pause");
        return 0;
}

当资源要释放时,p1节点释放的前提是p2释放,而p2的释放又依赖于p1,就形成了一个互相等待的局面,上升到操作系统的话,就等于进程之间形成了死锁,只不过这里是资源释放的依赖关系,而操作系统是资源竞争的关系。最终程序形成了循环引用,两个节点都无法释放资源,内存泄漏也就顺理成章。
问题一:循环引用问题
解决办法:所以此时需要利用weak_ptr来解决循环引用的问题,weak_ptr它指向的是一个由shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr的对象上,其不会改变shared_ptr的引用计数,一旦最后一个指向对象的shared_ptr被销毁,对象就会被销毁,即使有weak_ptr指向对象,对象还是被释放。

template<class T>
struct ListNode
{
        ListNode(T value)
        :_value(value)
        {
               cout << "ListNode()" << endl;
        }
        ~ListNode()
        {
               cout << "~ListNode()" << endl;
        }
        T _value;
        weak_ptr<ListNode<T>> _prev;
        weak_ptr<ListNode<T>> _next;
};
void test()
{
        shared_ptr<ListNode<int>> p1(new ListNode<int>(1));
        shared_ptr<ListNode<int>> p2(new ListNode<int>(2));
        cout << p1.use_count() << endl;
        cout << p2.use_count() << endl;
        p1->_next = p2;
        p2->_prev = p1;
        cout << p1.use_count() << endl;
        cout << p2.use_count() << endl;
}
int main()
{
        test();
        system("pause");
        return 0;
}

weak_ptr是一个不控制对象生命周期的智能指针,它指向一个由shared_ptr指向的对象,将一个weak_ptr绑定到由shared_ptr指向的对象上,它不会改变shared_ptr的引用计数,当引用计数等于0时,
对象就会被销毁,调用析构函数,即使weak_ptr指向对象,对象还是会释放。
问题二:线程安全问题
shared_ptr对象提供与内置类型一致的线程安全级别,一个shared_ptr指向的对象可以被多个线程进行“读”,一个shared_ptr指向的对象可以被多个线程写入,虽然这些看似是拷贝,但是导致线程不安全。(即使这些实 例是拷贝,而且共享下层的引用计数),任何其它的同时访问的结果会导致未定义行为。总结一下主要有3个方面。

1.同一个shared_ptr被多个线程“读“是安全的。
2.同一个shared_ptr被多个线程“写”是不安全的。
3.共享引用计数不同的shared_ptr被多个线程“写”是安全的。

问题三:内存泄漏问题
当我们用malloc申请出来的空间是无法释放的,因为malloc申请的空间只能用free来释放,而当我们打开一个文件指针,程序运行完毕后,需要关闭文件,否则会造成内存泄漏。
所以要来定制删除器,定制删除器还有一个原因是shared_ptr不支持动态数组管理,若要管理动态数组,则需自己定制删除器。

猜你喜欢

转载自blog.csdn.net/xiaodu655/article/details/85780240