对于shared_ptr
学习笔记的整理
一.管理具有共享所有权的资源
通过std::shared_ptr
智能指针访问的对象采用共享所有权来管理其生存周期,当最后一个指涉到某对象的std::shared_ptr
不再指向它的时候,该std::shared_ptr
会析构所指向的对象.
std::shared_ptr
可以通过访问某资源的引用计数
来确定自己是不是最后一个指向该资源的.
当然引用计数
也会有性能影响:
std::shared_ptr
的尺寸是裸指针的两倍.因为内部包含一个指向资源的裸指针,也有一个指向引用计数的裸指针.- 引用计数必须
动态分配
,我们可以从概念上来理解,引用计数与被指向的对象相关联,但是被指向的对象一无所知,所以引用技术只能在std::shared_ptr
托管. - 引用计数的递增和递减必须
原子操作
.主要是考虑多线程并发的问题,原子操作一般都是比非原子操作慢的.
std:: shared_ptr
与std::unique_ptr
不同的是,std::shared_ptr
的尺寸始终会是裸指针的二倍,std::unique_ptr
的大小和裸指针一样,但是如果有自定义的析构器并且这个析构器是一个函数对象,那么大小会增加1-2的字节大小
那么问题就来了std::shared_ptr
为什么不会扩大尺寸的大小?
因为自定义析构器会是在堆上分配,并且前文所提到的其中有一个指针指向引用技术.它并不是仅仅指向引用计数,它指向的是一个控制块.
如图:
一个对象的控制块由创建首个指涉到该对象的std::shared_ptr的函数来确定.
控制块的创建遵循了规则:
- std::make_shared 总是创建一个控制块.
- 具有专属权的指针构建std::shared_ptr , 会创建一个控制块.具有专属权的指针是没有控制块的,专属所有权的智能指针会被置空.
- 使用裸指针来创建shared_ptr , 创建一个控制块.
这样规则导致一个后果
: 从同一个裸指针来构造不止一个shared_ptr ,它会创建多个控制块,当然这就意味多重的引用计数,该对象会被析够多次.
所有我们在编程的时候,尽可能避免std::shared_ptr 的构造函数,尽可能使用std::make_shared
,除此之外还有一种特殊的情况我们需要考虑
std::vector<std::shared_ptr<test>> l;
class test{
public:
void process();
};
void test::process(){
l.emplace_back(this);
}
咋一看没有什么问题,但是仔细想的话 将一个this指针创建一个新的控制块,会创建多个控制块,从而造成未定义的行为.
std::shared_ptr
提供了一个一种基础措施,std::enable_shared_from_this
,当你希望一个托管到std::shared_ptr
的类可以通过this指针创建一个std::shared_ptr
时候,它将继承而来 std::enable_shared_from_this
将上述修改为l.emplace_back(shared_from_this)
二.对于类似与std::shared_ptr
但有可能空悬的指针使用std::weak_ptr
std::weak_ptr
像std::shared_ptr 一样运作,但是不影响引用计数,它指向的是控制块的弱引用,这种指针处理了一个问题,检查std::shared_ptr
所执行的对象有没有被析构
std::weak_ptr
是通过std::shared_ptr
来进行创建.两者指向的是同一个位置,共享同一个控制块
我们可以通过 lock
方法来检查指向的内存空间有没有被析够
std::weak_ptr
的应用场景:主要是为了解决std:: shared_ptr
的循环引用问题
三.优先选用std::make_unique
和 std::make_shared
,而非直接使用new
- 不用
new
的理由如下: - 使用
new
的版本违背了软件工程的一个重要原则: 重复撰写类型,源代码中的重复增加编译. - 与异常安全有关,
new
必须在shared_ptr
的构造函数之前来执行完毕,如果在new
之后,程序在运行期发生异常退出程序,那么就会有内存泄漏的问题.
总结:
- 使用
std::make_unique
和std::make_shared
来替换new - 使用
std::weak_ptr
来解决std::shared_ptr
的循环引用