概念
共享指针,即多个指针指向同一个内存;具体实现方式是采用的引用计数,即这块地址上每多一个指针指向他,计数加一;
引用计数可以跟踪对象所有权,并能够自动销毁对象。可以说引用计数是个简单的垃圾回收体系。
智能指针是模板类而不是指针。创建一个智能指针时,必须指针可以指向的类型,
智能指针实质就是重载了->和\操作符的类,由类来实现对内存的管理,确保即使有异常产生,也可以通过智能指针类的析构函数完成内存的释放。
可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。无论何时拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数析构函数(destructor)来完成销毁工作的。类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制此类型的对象销毁时做什么操作。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
完整的例子
1 |
|
析构
析构函数中删除内部原始指针,默认调用的是delete()函数。
1 | delete point; |
像这样申请的数组,应该调用delete []释放内存,而shared_ptr析构函数中默认delete并不能满足需求。
给shared_ptr添加自定义删除器:
1 |
|
误区:
不从 new 的返回值直接构造共享指针
1 | T *a = new T(); |
这样的话,ptr1 和 ptr2 的引用计数是单独算的,它们任意一个对象在析构的时候,都会销毁 a 所指的对象,a就为悬空指针,所以,这个对象会被“销毁两次”。因此报错。(make_shared类模板可以避免)
http://c.biancheng.net/view/7898.html
https://www.cnblogs.com/bandaoyu/p/14625038.html
线程安全探究
共享指针的线程安全问题
All member functions (including copy constructor and copy assignment) can be called by multiple threads on different instances of shared_ptr without additional synchronization even if these instances are copies and share ownership of the same object. If multiple threads of execution access the same shared_ptr without synchronization and any of those accesses uses a non-const member function of shared_ptr then a data race will occur; the shared_ptr overloads of atomic functions can be used to prevent the data race.
"Multiple threads can simultaneously read and write different shared_ptr objects, even when the objects are copies that share ownership
解释一下以上的说话,比如我们建立一个共享指针管理的对象:
1 | shared_ptr<int> ptr1 = make_shared<int>(); |
此时有两个逻辑块,不应该对待同样的处理。 一个是存储实际值的int,另一个是控制块,它存储使其工作的所有Shared_ptr <>工作原理。
只有控制块本身是线程安全的,也就是共享指针实体可以在不同的线程建立和销毁,共享指针的计数原则都是原子操作,但是如果对指向对象有写操作,那么就需要加锁了,看如下代码
1 | shared_ptr<int> global_instance = make_shared<int>(0); |
综上,共享指针的机制并不能保证多线程可以准确地访问资源,我们还是需要用同步机制比如st::mutex来使其线程安全。使用时我们要明确多个副本访问同一块内存有没有同步的问题
参考文献:
https://ofstack.com/C++/8983/full-analysis-of-shared_ptr-thread-safety.html
https://stackoverflow.com/questions/14482830/stdshared-ptr-thread-safety
https://en.cppreference.com/w/cpp/memory/shared_ptr