C++ 智能指针

unique_ptr<T,Deleter>

  1. 同时只能有一个unique_ptr指向创建的对象
  2. unique_ptr不可拷贝和拷贝赋值,可以移动和移动赋值
  3. 可以存放数组,存放数组时可以使用 [] 运算符访问数组元素
  4. 可以直接传给shared_ptr的构造函数,从而转换为shared_ptr
  5. 派生类的unique_ptr可以赋值给基类的unique_ptr(需要保证删除器相同)。 unique_ptr没有专用的类型转换函数,基类的unique_ptr不能赋值给派生类的unique_ptr。
  6. 可以用unique_ptr::release函数获取对象,并将对象与unique_ptr解除绑定。通过解除绑定,再重新创建新类型的unique_ptr,可以完成unique_ptr类型转换。
  7. 可以自定义删除器,删除器属于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>

  1. 多个shared_ptr同时指向同一个对象
  2. 支持拷贝赋值和移动赋值
  3. C++17开始支持数组,同时支持 [] 与索引访问数组元素
  4. 可以使用函数std::dynamic_pointer_cast、static_pointer_cast进行转换
  5. 一旦对象放入shared_ptr,则与shared_ptr始终绑定,不可以解绑
  6. 支持自定义删除器,删除器是存放在控制块中,删除器类型与shared_ptr的类型无关,不同删除器的shared_ptr可以相互赋值。
  7. shared_ptr内部使用了原子计数,所以如果可以使用移动尽量使用移动语义,可以减少原子变量的操作
  8. 使用make_shared可以让对象和控制块内存分配在同一块内存上,可以减少一次内存分配,提高效率。

实现

shared_ptr使用控制块实现,share_ptr内部会有两个指针,一个是当前类型数据对象,一个是控制块的指针。

控制块

控制块通过继承实现,内部使用了虚函数,shared_ptr内部存储的是基类的指针,与实际的控制块类型无关。控制块的类型与删除器deleter相关,shared_ptr并没有使用控制器类型,而是用了基类, 这就是为什么不同类型删除器的shared_ptr可以相互赋值。

ptr

控制块内部会存储一个分配内存的指针Ref_count::ptr,用于存储分配内存时的指针,这个指针和shared_ptr<T>::ptr大部分情况下时一样的,但也有不同的时候,主要有两种情况下不一样:

  1. 当类型T有多重继承,类型转换后不同基类的地址不相同,Ref_count::ptr始终指向分配内存时的指针,而shared_ptr<T>::ptr会根据实际基类类型改变。
  2. 使用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>

  1. weak_ptr内部变量和shared_ptr是相同的,但不可以直接访问对应的指针和对象。
  2. weak_ptr是shared_ptr的弱引用,会影响weak_ref引用计数,weak_ptr不会影响对象的生命周期,但是会影响控制块的生命周期。
  3. 可以通过expired函数检测当前对象是否已经释放
  4. 可以通过lock函数转换为shared_ptr,如果对象已经释放,返回null。
  5. 通过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

  1. enable_shared_from_this可以让对象获取对应的shared_ptr
  2. enable_shared_from_this内部会存储一个weak_ptr指针,当调用shared_from_this时,会使用weak_ptr构造一个shared_ptr并返回
  3. 如果对象没有放在shared_ptr中,调用shared_from_this时会抛出异常,因为内部的weak_ptr已经expired,内部通过通过shared_ptr(weak_ptr)构造shared_ptr时会抛出异常。
  4. 可以通过函数weak_from_this来判断当前对象是否在shared_ptr中。
  5. 构造函数调用shared_from_this,因为此时对象还没有放入shared_ptr,weak_ptr为expired,会导致抛出异常。
  6. 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

std::weak_ptr - cppreference.com

std::enable_shared_from_this - cppreference.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值