unique_ptr<T,Deleter>
- 同时只能有一个unique_ptr指向创建的对象
- unique_ptr不可拷贝和拷贝赋值,可以移动和移动赋值
- 可以存放数组,存放数组时可以使用 [] 运算符访问数组元素
- 可以直接传给shared_ptr的构造函数,从而转换为shared_ptr
- 派生类的unique_ptr可以赋值给基类的unique_ptr(需要保证删除器相同)。 unique_ptr没有专用的类型转换函数,基类的unique_ptr不能赋值给派生类的unique_ptr。
- 可以用unique_ptr::release函数获取对象,并将对象与unique_ptr解除绑定。通过解除绑定,再重新创建新类型的unique_ptr,可以完成unique_ptr类型转换。
- 可以自定义删除器,删除器属于unique_ptr类型的一部分,不同删除器的unique_ptr不能相互赋值。
auto createUniquePointer()
{
constexpr auto deleter = [](int* p) { delete p; };
using DeleterType = decltype(deleter);
auto up = std::unique_ptr<int, DeleterType>(new int, deleter);
return up;
}
shared_ptr<T>
- 多个shared_ptr同时指向同一个对象
- 支持拷贝赋值和移动赋值
- C++17开始支持数组,同时支持 [] 与索引访问数组元素
- 可以使用函数std::dynamic_pointer_cast、static_pointer_cast进行转换
- 一旦对象放入shared_ptr,则与shared_ptr始终绑定,不可以解绑
- 支持自定义删除器,删除器是存放在控制块中,删除器类型与shared_ptr的类型无关,不同删除器的shared_ptr可以相互赋值。
- shared_ptr内部使用了原子计数,所以如果可以使用移动尽量使用移动语义,可以减少原子变量的操作
- 使用make_shared可以让对象和控制块内存分配在同一块内存上,可以减少一次内存分配,提高效率。
实现
shared_ptr使用控制块实现,share_ptr内部会有两个指针,一个是当前类型数据对象,一个是控制块的指针。
控制块
控制块通过继承实现,内部使用了虚函数,shared_ptr内部存储的是基类的指针,与实际的控制块类型无关。控制块的类型与删除器deleter相关,shared_ptr并没有使用控制器类型,而是用了基类, 这就是为什么不同类型删除器的shared_ptr可以相互赋值。
ptr
控制块内部会存储一个分配内存的指针Ref_count::ptr,用于存储分配内存时的指针,这个指针和shared_ptr<T>::ptr大部分情况下时一样的,但也有不同的时候,主要有两种情况下不一样:
- 当类型T有多重继承,类型转换后不同基类的地址不相同,Ref_count::ptr始终指向分配内存时的指针,而shared_ptr<T>::ptr会根据实际基类类型改变。
- 使用shared_ptr<T>( const shared_ptr<Y>& ref, T* ptr )重新构造的智能指针,这里构造的shared_ptr<T>和传入的 ref 使用的是相同的控制块,实际的类型可以是对象内部的任意数据。
strong_ref
strong_ref是shared_ptr的引用计数,当前strong_ref变为0时会调用对象的析构函数,但不一定会释放对象的内存,
如果shared_ptr创建时使用的是new 构建的对象或使用了自定义删除器则会释放对象的内存。
如果shared_ptr是通过make_shared创建的,此时对象和控制块是同时分配在同一块内存上,这种情况是否释放内存还需要看weak_ref的值。
weak_ref
weak_ref是weak_ptr的引用计数,当strong_ref和weak_ref都为0时则会释放控制块的内存
weak_ptr<T>
- weak_ptr内部变量和shared_ptr是相同的,但不可以直接访问对应的指针和对象。
- weak_ptr是shared_ptr的弱引用,会影响weak_ref引用计数,weak_ptr不会影响对象的生命周期,但是会影响控制块的生命周期。
- 可以通过expired函数检测当前对象是否已经释放
- 可以通过lock函数转换为shared_ptr,如果对象已经释放,返回null。
- 通过shared_ptr<T>(weak_ptr<T> wp)构造shared_ptr时,如果weak_ptr已经expired则会抛出异常
线程安全
shaded_ptr、week_ptr,包括unique_ptr,他们自身是没有锁和原子操作,自身不是线程安全。shader_ptr赋值是有多部操作,如果把一个shader_ptr的引用或者地址(shader_ptr对象的地址)传给其他线程使用,当以一个线程在给shared_ptr赋值时,另一个线程在使用shader_ptr指向的对象可能会使用中间状态导致崩溃。week_ptr和unique_ptr也是同理。
shared_ptr和week_ptr的控制块(Control Block),内部使用了原子变量操作,控制块引用计数是线程安全的。线程安全也仅限于引用计数,如果两个线程同时操作一个控制块,一个线程引用计数减少到0准备释放内存,另一个线程又开始复制,这种情况也可能出现问题。
多线程使用shared_ptr
如果多线程使用shaded_ptr、week_ptr,不能使用引用或指针传递智能指针,需要传递拷贝,通过拷贝可以保证智能指针的线程安全。
通过拷贝可以保证当前线程持有shared_ptr,也就是只要当前线程不释放shared_ptr引用计数永远不会为0,其他线程也就不可能释放内存,也就不会出现一个线程在释放内存,另一个线程又在复制。也就是说多个在不同线程的shared_ptr指向同一个对象,使用同一个控制块,shared_ptr可以正常工作,可以保证引用计数正确,可以正常释放内存,不会出现多次释放内存或内存泄漏
enable_shared_from_this
- enable_shared_from_this可以让对象获取对应的shared_ptr
- enable_shared_from_this内部会存储一个weak_ptr指针,当调用shared_from_this时,会使用weak_ptr构造一个shared_ptr并返回
- 如果对象没有放在shared_ptr中,调用shared_from_this时会抛出异常,因为内部的weak_ptr已经expired,内部通过通过shared_ptr(weak_ptr)构造shared_ptr时会抛出异常。
- 可以通过函数weak_from_this来判断当前对象是否在shared_ptr中。
- 构造函数调用shared_from_this,因为此时对象还没有放入shared_ptr,weak_ptr为expired,会导致抛出异常。
- shared_ptr构造函数内部会识别类型是否继承至enable_shared_from_this,如果是继承至enable_shared_from_this则会设置内部的weak_ptr。
enable_shared_from_this只需要基类继承,派生类无需再次继承,需要使用public继承方式。如果基类和派生类都继承了enable_shared_from_this会导致shared_ptr识别类型失败,而无法使用shared_from_this。
示例
std::cout << "------------- enable_shared_from_this-----------------" << std::endl;
struct A : public std::enable_shared_from_this<A>
{
virtual ~A() = default;
std::weak_ptr<A> wpA() { return weak_from_this(); }
std::shared_ptr<A> spA() { return shared_from_this(); }
};
struct B : public std::enable_shared_from_this<B>, public A
{
std::weak_ptr<B> wpB() { return std::enable_shared_from_this<B>::weak_from_this();}
std::shared_ptr<B> spB() { return std::enable_shared_from_this<B>::shared_from_this();}
};
struct C : public A
{
std::weak_ptr<C> wpC() { return std::dynamic_pointer_cast<C>(weak_from_this().lock());}
std::shared_ptr<C> spC() { return std::dynamic_pointer_cast<C>(shared_from_this());}
};
std::shared_ptr<B> spB = std::shared_ptr<B>(new B);
std::cout << "B, wpB weak_from_this expired: " << spB->wpB().expired() << std::endl;
std::cout << "B, wpA weak_from_this expired: " << spB->wpA().expired() << std::endl;
std::shared_ptr<C> spC = std::shared_ptr<C>(new C);
std::cout << "C, wpC weak_from_this expired: " << spC->wpC().expired() << std::endl;
std::cout << "C, wpA weak_from_this expired: " << spC->wpA().expired() << std::endl;
运行结果
reference
std::unique_ptr - cppreference.com
std::shared_ptr - cppreference.com