简单总结,详见参考资料。
引用计数的实现方式
引用计数是与对象绑定的,并且可能有多个shared_ptr绑定同一对象,为了达到同时更新多个shared_ptr的引用计数,所以需要让他们指向同一个引用计数,所以只能在堆上另外分配空间来存放引用计数。
为了保证多线程读同一个shared_ptr是安全的,引用计数的增减是原子操作。
线程安全性
shared_ptr 的线程安全级别和内建类型、标准库容器、std::string 一样,即:
一个 shared_ptr 对象实体可被多个线程同时读取;
两个 shared_ptr 对象实体可以被两个线程同时写入,“析构”算写操作;
如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁;
为什么shared_ptr不能并发读写:因为 shared_ptr 有两个数据成员(引用计数、对象指针),读写操作不能原子化。
具体发生多线程问题的场景可以自己推导,更详细的说明可以看陈硕写的文章:
为什么多线程读写 shared_ptr 要加锁?
enable_shared_from_this的实现
一个主要的场景是保证异步回调函数中操作的对象仍然有效。
std::enable_shared_from_this<T> 有一个std::weak_ptr<T>的成员,实际上在构造std::enable_shared_from_this<T>时,并没有初始化std::weak_ptr<T>成员,而是在用这个std::enable_shared_from_this<T>去构造std::shared_ptr的时候,去构造并初始化这个std::weak_ptr<T>成员。所以这也就是为什么cppreference中说的这个对象必须是std::shared_ptr管理的,因为这个对象不是通过std::shared_ptr来管理,那么std::weak_ptr是未初始化的,无法通过其提升为std::shared_ptr对象。
如何判断是否继承了shared_from_this?模板类型推导:根据是否有继承特定类型实例化不同的模板实现;
多继承场景下使用虚继承解决问题;
make_shared/new
与直接使用new相比,make函数不存在代码重复且具备异常安全性,std::make_shared和std::allocate_shared可以生成更快更短的目标码。
对象和控制块分配在一块内存上,减少了内存分配的次数。但是这并不一定是好事,因为这导致对象和控制块占用的内存也要一次回收掉。即,如果还有std::weak_ptr存在,控制块就要在,对象占用的内存也没办法回收。如果对象比较大,且std::weak_ptr在对象析构后还可能长期存在,那么这种开销是不可忽视的。
在需要自定义删除器及以大括号初始化时make函数无能为力。
更详细可以阅读《Effective Moden C++》Item 21。
pointer_cast的实现
智能指针的类型转换无法通过static_cast等实现,需要使用static_pointer_cast方法(想一下如果取裸指针出来static_cast会发生什么)。static_pointer_cast是通过shared_ptr一个特殊的构造函数实现的。
template< class T, class U >
std::shared_ptr<T> static_pointer_cast( const std::shared_ptr<U>& r ) noexcept
{
auto p = static_cast<typename std::shared_ptr<T>::element_type*>(r.get());
return std::shared_ptr<T>(r, p);
}
shared_ptr与deleter
shared_ptr<Foo> sp(new Foo)在构造sp的时候捕获了Foo的析构行为,意味着:
-
shared_ptr<void> 可以指向并安全地管理(析构或防止析构)任何对象,派生类没有虚析构函数也能被正确析构
shared_ptr<Foo> sp1(new Foo); // ref_count.ptr 的类型是 Foo*
shared_ptr<void> sp2 = sp1; // 可以赋值,Foo* 向 void* 自动转型
sp1.reset(); // 这时 Foo 对象的引用计数降为 1
此后 sp2 仍然能安全地管理 Foo 对象的生命期,并安全完整地释放 Foo,不会出现 delete void* 的情况,因为 delete 的是 ref_count.ptr,不是 sp2.ptr。
std::shared_ptr<void>的工作原理
- shared_ptr进行资源销毁时,总会调用创建智能指针的那个DLL中的delete,这意味着shared_ptr可以随意地在DLL间传递而不需担心跨DLL的问题。(Effective C++ Item18)
参考资料:
为什么多线程读写 shared_ptr 要加锁
std::shared_ptr<void>的工作原理
《Effective C++》
《Effective Modern C++》
网友评论