C++ 智能指针

什么是智能指针

智能指针是一种运行在C++中的对象模拟指针行为的类,它提供了比原始指针更为先进和安全的内存管理模式。与传统的原始指针相比,智能指针能够自动管理其所指对象的生命周期,当智能指针的生命周期结束时,它们可以自动释放或删除其所指向的内存资源,从而减少内存泄漏的风险。

C++标准库提供了几种不同类型的智能指针,主要包括std::unique_ptrstd::shared_ptrstd::weak_ptr,每种智能指针都有其特定的使用场景和目的。

智能指针的设计

在对象构造的时候获取资源,在对对象析构的时候释放资源,利用对象的生命周期来控制资源,即RAII特性

对 运算符 *  和 运算符 -> 进行重载,使智能指针也有指针一样的行为

原理及模拟实现

auto_ptr

思想

通过管理权转移的方式,保证一个资源在任何时刻都只有一个对象在对其进行管理

设计

在拷贝构造函数中,用传入对象管理的资源构造当前对象,并将原来的指针置空

在拷贝赋值函数中,先将当前资源释放,再接管传入对象的资源,并将原来的指针置空

缺点

不能对原来的ptr进行访问,否则程序崩溃

unique_ptr

思想

防止拷贝

设计

将拷贝构造和拷贝赋值私有,或加delete

缺点

无法对unique_ptr对象进行拷贝

shared_ptr

思想

引用计数,支持多个对象一起对同一个资源进行管理

设计

在构造时获取资源,并将该资源对应的引用计数设置为1

在拷贝构造中,与传入对象一起管理它管理的资源,同时将该资源的引用计数++

在拷贝赋值中,先将当前对象管理的资源对应引用计数--(如果减为0则需要释放原来的资源),然后再与传入的对象一起管理它的资源,同时将该资源对应的引用计数++

析构时,将管理资源对应的引用计数--,如果减为0则需要释放资源

线程安全相关

++和--都不是原子的,需要加锁对引用计数进行保护

定制删除器

shared_ptr不只是管理new的内存对象,也可能是new[],或是一个文件指针

因此在创建shared_ptr对象时,可以传入一个可调用对象,用该对象完成指定的资源释放

循环引用问题

什么是循环引用?如何解决? 解决原理

循环引用是指两个或多个对象之间形成了一个引用循环,从而导致它们不能被正常回收释放,这通常出现在使用智能指针进行内存管理时。特别是在使用std::shared_ptr进行对象管理时,如果两个对象互相持有对方的std::shared_ptr引用,那么它们的引用计数永远不会降到0,导致对象无法被自动释放,从而引起内存泄露。

假设有两个类ClassAClassB,它们互相持有对方的指针:

#include <memory>

class ClassB; // 前向声明

class ClassA {
public:
    std::shared_ptr<ClassB> bPtr; // ClassA 持有 ClassB 的共享指针
    ~ClassA() { std::cout << "ClassA destroyed\n"; }
};

class ClassB {
public:
    std::shared_ptr<ClassA> aPtr; // ClassB 持有 ClassA 的共享指针
    ~ClassB() { std::cout << "ClassB destroyed\n"; }
};

// 在某个函数中构造循环引用
void createCycle() {
    auto a = std::make_shared<ClassA>();
    auto b = std::make_shared<ClassB>();
    a->bPtr = b;
    b->aPtr = a;
}

 在createCycle函数中,ab互相持有对方的std::shared_ptr,形成了循环引用。由于它们的引用计数无法降到0,所以即使createCycle函数执行完毕,ab也不会被销毁,导致内存泄漏。

 要解决这个问题,可以将其中一个类持有对另一个类的std::weak_ptr。假设我们修改ClassB使其持有ClassAstd::weak_ptr

class ClassB {
public:
    std::weak_ptr<ClassA> aPtr; // 将 shared_ptr 改为 weak_ptr
    ~ClassB() { std::cout << "ClassB destroyed\n"; }
};

 这样,当createCycle函数执行完毕,尽管ClassB的实例仍持有ClassA实例的引用,但这个引用是一个std::weak_ptr,它不会增加ClassA的引用计数。因此,当a的引用计数降到0时,它会被正常销毁。同理,当b的引用计数降到0时,由于b不再增加a的引用计数,b也会被正常销毁。这样,就成功避免了内存泄漏的问题。

weak_ptr

思想

通过不增加/减少引用计数的方式,解决shared_ptr的循环引用问题



设计

  • 提供一个无参的构造函数
  • 支持用shared_ptr对象拷贝构造weak_ptr对象,构造时获取shared_ptr对象管理的资源
  • 支持用shared_ptr对象拷贝赋值给weak_ptr对象,赋值时获取shared_ptr对象管理的资源

方式

  1. 监视_shared_ptr管理的资源

    std::weak_ptr 设计用于监视一个资源,而该资源由一个或多个 std::shared_ptr 实例“拥有”。如果所有的 std::shared_ptr 都已被销毁,即引用计数归零, std::weak_ptr 将知道它所指向的资源不再存在。

  2. 提供对管理的资源的临时访问

    通过调用 std::weak_ptr 的 lock() 方法,可以尝试获取一个 std::shared_ptr,如果资源仍然存在(也就是至少有一个 std::shared_ptr 仍然存在),lock() 会成功返回一个可用的 std::shared_ptr。如果资源已经被释放,lock() 会返回一个空的 std::shared_ptr

  3. 不影响资源的引用计数

    由于 std::weak_ptr 不增加资源的引用计数,它可以用来解决 std::shared_ptr 之间可能出现的循环引用问题。当转换为 std::shared_ptr 失败(例如,资源已被释放)时,它不会破坏资源的自动销毁机制。

一些情况

在智能指针管理的链表结构中,使用std::weak_ptr生成一个std::shared_ptr(通过lock()方法)来临时管理节点,并不会直接影响到原有的std::weak_ptrstd::weak_ptr的生命周期和它是否被转换为std::shared_ptr无关。std::weak_ptr的存在不会增加对象的引用计数,因此,它本身并不管理对象的生命周期,而是用来观察和访问由std::shared_ptr管理的对象。

当你通过lock()std::weak_ptr得到一个std::shared_ptr时,得到的std::shared_ptr会增加对象的引用计数。这意味着,只要这个临时的std::shared_ptr存在,它所指向的对象就不会被销毁。当这个std::shared_ptr离开作用域或被显式销毁后,对象的引用计数会减少。如果引用计数变为0(意味着没有任何std::shared_ptr指向这个对象),对象会被自动销毁。

在这个过程中,std::weak_ptr本身的存在与否与通过它生成的std::shared_ptr没有直接关系。std::weak_ptr的消失也是独立于这个过程。std::weak_ptr会在它自己的作用域结束时销毁,或者当你将它赋予新值或显式重置时。

总之:

  • std::weak_ptr通过lock()方法生成了一个std::shared_ptr时,这个生成的std::shared_ptr会临时增加对象的引用计数。
  • 这个临时的std::shared_ptr存在期间,对象是安全的,不会被销毁。
  • 当这个临时的std::shared_ptr离开作用域或被销毁后,如果它是最后一个指向对象的std::shared_ptr,对象会被销毁。
  • 原有的std::weak_ptr与此过程无关,它的生命周期由其作用域和程序逻辑决定。
  • 13
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值