对C++智能指针的一些学习总结,理解的梳理

智能指针如unique_ptr,shared_ptr和weak_ptr用于自动管理动态内存,防止内存泄漏。它们基于RAII原则,在对象生命周期内管理资源,确保在不再需要时正确释放。shared_ptr支持共享所有权,通过引用计数跟踪多个智能指针对同一对象的所有权,而unique_ptr则确保资源的独特性,不支持共享。weak_ptr则解决shared_ptr可能导致的循环引用问题。
摘要由CSDN通过智能技术生成

为什么有智能指针,是为了解决什么问题?(内存泄漏)

对裸指针操作容易出现以下情况:

  • 访问无效数据(指针未初始化 野指针;指针指向的资源被释放 悬空指针,访问非法内存)
  • 指针越界(直接对原始指针加减)
  • 忘记释放分配的内存(狭义内存泄漏)

以上各种不当行为造成运行错误、内存泄漏、资源丢失等问题。
智能指针的出现就是为了能够安全的使用动态对象,确保在离开指针所在作用域时,自动正确的销毁动态分配的对象,防止内存泄漏(尽可能,非要取出裸指针运算那也没办法)。

如何解决上述问题? (RAII)

那肯定得看看它内部是通过什么原理来实现的,这里仅从思想上简要描述:

  • 智能指针采用RAII方法, 将资源生命周期与对象生命周期绑定,在构造函数中初始化资源,离开作用域时在构造函数中自动释放内存。

    这应该是智能指针的核心了。类似的采用RAII机制的还有C++中的一些互斥锁,如lock_guard, unique_lock

  • 使用代理模式包装裸指针,重载*和->操作符,使其用起来和用原始指针保持一样的手感。

有哪些智能指针?

  • 几种智能指针: auto_ptr, unique_ptr, shared_ptr, weak_ptr

    • auto_ptr(C++11弃用,C++17移除)
      弃用auto_ptr的原因:

      • 拷贝构造函数和赋值语句的意义不明确,使用浅拷贝方式时,两个对象拥有同一块资源,在对象析构时资源会被释放两次造成程序崩溃;而如果更改成深拷贝的方式,会导致原对象失去对资源的所有权
      • 析构函数也不能判断是析构一个对象还是一组对象

      可以认为auto_ptr只是应用了RAII机制,其他的细节没有考虑到

    • unique_ptr

      • 唯一性智能指针,指针独占使用,更加安全
      • 删除了拷贝构造和赋值操作符重载,增加了移动构造和移动赋值
    • shared_ptr

      • 共享性智能指针,指针共享使用,有引用计数,但有少量管理成本,不能过度使用
      • 使用make_unique()和make_shared()创建智能指针(C++14标准),可以用auto简化声明
    • weak_ptr:弱引用指针
      主要是为了解决使用shared_ptr共享指针时出现的循环引用的问题

  • 智能指针的演变,之间的逻辑关系

    autoptr之所以会出现问题,最大的原因是它它允许拷贝语义的同时,没有提供对拷贝带来的问题的解决方法,可以理解为它只是用了RAII机制来包装裸指针,除此之外没有考虑其他细节。

    那么针对它拷贝语义带来的两个指针指向同一资源的问题,有两种解决思路:

    • 一种解决方法就是直接禁用拷贝,这是unique_ptr的做法,它删除了拷贝构造和拷贝赋值,同时添加了移动构造和移动赋值,只允许一个指针独占一个资源,不允许共享;
    • 另一种解决方法是允许共享,但提供引用计数的功能,使得能够知道有多少个指针在共享同一资源,这是sharedptr的做法
  • 进一步理解shared_ptr(目前是shared_ptr,其他的待补充)

    • 功能描述&实现原理:
      • shared_ptr可以使多个指针指向同一个对象,用引用计数器记录一共有多少个指针共享该对象,计数为0时对象自动释放对象资源并将原始指针置为空,避免悬空指针的产生。

      • 具体实现:
        这个类的内部主要有两个指针成员,一个指向资源;一个指向资源的引用计数。
        可以从从构造函数、拷贝构造函数、赋值、析构函数的角度来理解其代码实现的大致思路:

        1. 直接创建新对象时的初始化
          当创建新的智能指针对象时,在构造函数中初始化指向资源的指针,并将引用计数置为1(当然如果创建的是空对象,指针为空且引用计数为0)
        2. 拷贝形式的初始化
          当使用已有的shared_ptr对象初始化一个新的shared_ptr对象时,在拷贝构造函数中,拷贝资源指针和引用计数器指针,并将引用计数+1.
          (note个人理解:这里的拷贝是浅拷贝,直接拷贝指针,而不是指针指向的值,如果使用深拷贝,那么每个shared_ptr对象将会独立地持有资源和引用计数器,而不是共享)
        3. 拷贝赋值
          当使用一个shared_ptr对另一个进行赋值时,赋值操作符左操作数的引用计数-1,右操作数的引用计数+1。左操作数的引用计数-1后若为0,需要先释放资源。
        4. 析构
          当任何 shared_ptr 对象超出作用域时,则在其析构函数中,将关联指针的引用计数减1。如果引用计数变为0,则表示没有其他 shared_ptr 对象与此内存关联,在这种情况下,使用delete释放资源,并将两个指针成员置为空。
        5. 容易出现的问题:
          • 循环引用
          • 释放两次:如果再初始化shared_ptr时是采用裸指针直接初始化的方式,可能造成使用同一个裸指针重复初始化了多个智能指针,造成每个智能指针都有独立的引用计数,当其中某个计数清零调用析构释放内存后,会造成悬空指针(另外的智能指针并不知道其指向的资源已经被释放了),剩下的智能指针再次释放资源时就会出现重复释放的问题。
// 智能指针的几种初始化方法,以shared_ptr为例

// 使用make_shared创建(推荐)
shared_ptr<MYCLASS> foo = make_shared<MYCLASS> ();

// 从原始指针创建(可能出错)
int* p = new int(10);
shared_ptr<int> ptr1(p);  // 调用构造函数,开辟新的引用计数资源
shared_ptr<int> ptr1_1(p);  // 此时有两个智能指针指向同一个资源,但各自的引用计数却是独立的。

// 在参数中new出临时的指针变量传递
shared_ptr<int> ptr2(new int(20));

// 用另一个智能指针初始化
shared_ptr<int> ptr3(ptr2);  // 调用拷贝构造函数,不开辟新的引用计数资源,只是在原基础上计数+1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值