这里能解决的办法就是加锁:
https://legacy.cplusplus.com/reference/mutex/mutex/?kw=mutex
#pragma once #include<iostream> #include<thread> #include<mutex> using namespace std; template<class T> class Shared { public: //RALL Shared(T* ptr) :_ptr(ptr) ,_pcount(new int(1)) ,_pmutex(new mutex) {} Shared(const Shared<T>& ptr) :_ptr(ptr._ptr) ,_pcount(ptr._pcount) ,_pmutex(ptr._pmutex) { _pmutex->lock(); (*_pcount)++; _pmutex->unlock(); } Shared<T>& operator=(const Shared<T>& ptr) { if (this->_ptr != ptr._ptr) { _pmutex->lock(); if (--(*_pcount) == 0)//如果被赋值的对象是指向的资源是最后一个对象记得释放原来的空间 { delete _ptr; delete _pcount; } _ptr = ptr._ptr; _pcount = ptr._pcount; _pmutex = ptr._pmutex; (*_pcount)++; _pmutex->unlock(); } return *this; } ~Shared() { _pmutex->lock(); bool flag = false; if (--(*_pcount) == 0) { delete _ptr; delete _pcount; flag = true; } _pmutex->unlock(); if (flag == true) delete _pmutex; } int ppcount()//引用计数的值 { return *_pcount; } //像指针一样 T* operator->() { return _ptr; } T& operator*() { return *_ptr; } T& operator[](size_t pos) { return _ptr[pos]; } private: T* _ptr; int* _pcount; mutex* _pmutex;//锁的指针 }; void test() { int n = 10000; Shared<int> sp1(new int(0)); thread t1([&]() { for (int i = 0; i < n; i++) { Shared<int> sp2(sp1); } }); thread t2([&]() { for (int i = 0; i < n; i++) { Shared<int> sp3(sp1); } }); t1.join(); t2.join(); cout << sp1.ppcount() << endl;//正常来说,结果应该是1 }
shared_ptr是会自动保护引用计数安全的,但是并不会保护指向资源安全:
#include<iostream> #include<thread> #include<memory> #include<mutex> using namespace std; struct Date { int _year = 0; int _month = 0; int _day = 0; }; void test() { int n = 10000; shared_ptr<Date> sp1(new Date); mutex mtx; thread t1([&]() { for (int i = 0; i < n; i++) { shared_ptr<Date> sp2(sp1); //mtx.lock(); sp2->_year++; sp2->_month++; sp2->_day++; //mtx.unlock(); } }); thread t2([&]() { for (int i = 0; i < n; i++) { shared_ptr<Date> sp3(sp1); //mtx.lock(); sp3->_year++; sp3->_month++; sp3->_day++; //mtx.unlock(); } }); t1.join(); t2.join(); cout << sp1->_day << endl; cout << sp1->_month << endl; cout << sp1->_year << endl; } int main() { test(); return 0; }
这个时候就要在资源进行++或者- - 操作的地方进行加锁和解锁了。(注释的地方就是)
循环引用问题
#include<iostream> #include<memory> using namespace std; struct ListNode { int val; shared_ptr<ListNode> _prev; shared_ptr<ListNode> _next; ~ListNode() { cout << "~ListNode()" << endl; } }; int main() { shared_ptr<ListNode>p1(new ListNode); shared_ptr<ListNode>p2(new ListNode); p1->_next = p2; p2->_prev = p1; return 0; }
正常来说,p1p2应该被释放了才对,但是这里并没有,所以也就造成了内存泄露。
这是因为:
出了作用域p1,p2会销毁,然后剩下ListNode中的内容;
prev与next的销毁是ListNode的空间销毁了才会销毁,这里他们互相指着,没有任何一个引用计数可以变为0,所以无法释放内存。
weak_ptr
https://legacy.cplusplus.com/reference/memory/weak_ptr/?kw=weak_ptr
这个智能指针就是为了解决这个问题而诞生的。
#include<iostream> #include<memory> using namespace std; struct ListNode { int val; weak_ptr<ListNode> _prev; weak_ptr<ListNode> _next; ~ListNode() { cout << "~ListNode()" << endl; } }; int main() { shared_ptr<ListNode>p1(new ListNode); shared_ptr<ListNode>p2(new ListNode); p1->_next = p2; p2->_prev = p1; return 0; }
这个是不参与资源管理,但是可以访问,也就是说不添加引用计数。
也就相当于上面模拟实现按的shared_ptr拷贝和赋值不增加引用计数,但是会检查指向的资源是否过期等等。
定制删除器
#include<iostream> #include<memory> #include<string> using namespace std; int main() { shared_ptr<string> sp1(new string[10]);//这里开辟的类型与库中自带的释放类型不匹配 return 0; }
整个程序就崩溃掉了。
这个时候就需要一种叫做定制删除器,类似于仿函数。
在库中这一条最后一个参数就是定制删除器,传入的是一个对象。
template<class T> struct Delete { void operator()(const T* ptr) { delete[] ptr; std::cout << "delete [] " << ptr << std::endl; } }; int main() { std::shared_ptr<int> sp1(new int[10], Delete<int>()); return 0; }
C++11和boost中智能指针的关系
- C++ 98 中产生了第一个智能指针auto_ptr。
- C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr。
- C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
- C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。