智能指针unique_ptr/shared_ptr/weak_ptr

1. 智能指针模板类

1.1 使用智能指针

  • 智能指针是行为类似于指针的类对象,可帮助管理动态内存分配。

  • 这三个智能指针模板(auto_ptr、unique_ptr和shared_ptr)都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete来释放内存。因此,如果将new返回的地址赋给这些对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动被释放。

  • 如果指针ps是对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正是auto_ptr、unique_ptr和shared_ptr背后的思想。模板auto_ptr是C++98提供的解决方案,C++11已将其摒弃,并提供了另外两种解决方案。

1.2 C++11的智能指针

  • unique_ptr

    不允许多个指针共享资源,可以用标准库中的move函数转移指针;

  • shared_ptr

    多个指针共享资源;

  • weak_ptr

    可复制shared_ptr,但其构造或者释放对资源不产生影响。

1.3 有关智能指针的注意事项

两个常规指针指向同一个string对象,是不可接受的,因为程序将试图删除同一对象两次,要避免这种问题,方法有多种:

  • 定义赋值运算符,使之执行深度复制。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本;

  • 建立所有权概念(ownership),对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是auto_ptr和unique_ptr的策略。但unique_ptr比auto_ptr更严格。

    std::auto_ptr<string> p1(new string("auto"));    //#1
    std::auto_ptr<string> p2;                        //#2
    p2 = p1;                                         //#3
    
    std::unique_ptr<string> p3(new string("auto"));  //#4
    std::auto_ptr<string> p4;                        //#5
    p4 = p3;                                         //#6
    

​ 注1:p1所有权被剥夺后,如程序试图再使用p1,将导致程序崩溃。编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。

​ 注2:程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值(很快被销毁,没有机会使用它来访问无效的数据),编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做。

​ 注3:相比于auto_ptr,unique_ptr还有一个优点,有一个可用于数组的变体。必须将delete和new配对,将delete[]和new[]配对。模板auto_ptr使用delete而不是delete[],因此只能与new一起使用,不能与new[]一起使用。但unique_ptr有使用new[]和delete[]的版本。

  • 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数(reference counting)。赋值时计数将加1,而指针过期时计数将减1。仅当最后一个指针过期时(引用计数为0),才调用delete。这是shared_ptr采用的策略。

  • 使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[]分配内存时,不能使用它们。不使用new分配内存时,不能使用auto_ptr或shared_ptr。

  • 不使用new和new[]分配内存时,不能使用unique_ptr。

1.4 选择智能指针

  • 如果程序要使用多个指向同一个对象的指针,应选择shared_ptr;很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,而不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定)。

  • 如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr;如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择;这样所有权将转让给接受返回值得unique_ptr,而该智能指针将负责调用delete。

  • 模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。

1.5 unique_ptr的操作

  • 使用原始指针创建unique_ptr对象:要创建非空的unique_ptr对象,需要在创建对象时在其构造函数中传递原始指针。

  • 使用 std::make_unique创建unique_ptr对象/C++14: std::make_unique<>()是C++14引入的新函数。

  • 创建一个空的unique_ptr对象:创建一个空的unique_ptr对象,因为没有与之关联的原始指针,所以它是空的。

  • 释放关联的原始指针:在unique_ptr对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。

  • 重置 unique_ptr 对象:在unique_ptr对象上调用reset()函数将重置它,即它将释放delete关联的原始指针并使unique_ptr 对象为空。

  • 获取被管理对象的指针:使用get()·函数获取管理对象的指针。

std::unique_ptr<Task> taskPtr2(new Task(55));

std::unique_ptr<Task> taskPtr6 = std::make_unique<Task>(88);

1.6 weak_ptr的操作

weak_ptr可以从一个shared_ptr 或者另一个对weak_ptr象构造,获得对象的观测权,但不管理对象,它的构造不会引起指针引用计数的增加。

weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。

使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。

使用场景

弱引用特性,不拥有对象,只有延迟到尝试调用lock()时才会有可能临时拥有对象:

  • 只是持有一个没有拥有权的被shared_ptr 托管的对象。
  • 只有调用lock()创建shared_ptr 指针时才会引用实际对象。
  • 在lock()成功时会延长shared_ptr 对象的生命周期,因为它递增了一个引用计数。
  • 在shared_ptr 主指针结束后,如果成功的指针还存在weak_ptr,那么这时候还有lock()的代码调用,引用计数仍然递增。

2. std::make_shared

  • C++11 中引入了智能指针, 同时还有一个模板函数 std::make_shared 可以返回一个指定类型的 std::shared_ptr。与 std::shared_ptr 的构造函数相比有以下优点:提高性能,异常安全。
    在这里插入图片描述
std::shared_ptr<Widget> spw(new Widget);
  • 这段代码需要分配两次内存。每个std::shared_ptr都指向一个控制块,控制块包含被指向对象的引用计数以及其他东西。控制块内存是在std::shared_ptr的构造函数中分配的。故直接使用new,需要一块内存分配给Widget,另一块内存分配给控制块。

在这里插入图片描述

auto spw = std::make_shared<Widget>();
  • 一次分配就足够了。这是因为std::make_shared申请一个单独的内存块来同时存放Widget对象和控制块。这个优化减少了程序的静态大小,因为代码只包含一次内存分配的调用,并且这会加快代码的执行速度,因为内存只分配了一次。另外,使用std::make_shared消除了一些控制块需要记录的信息,这样潜在地减少了程序的总内存占用。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>
#include <vector>
#include <Eigen/Core>


typedef Eigen::Matrix<float, 3, 1> Vector3f;
typedef Eigen::Matrix<int, 3, 1> Vector3d;

void check(std::weak_ptr<int> &wp)
{
    std::shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
    if (sp != nullptr)
    {
        std::cout << "still: " << *sp << std::endl;
    }
    else
    {
        std::cout << "still: " << "pointer is invalid" << std::endl;
    }
}


void mytest()
{
    std::shared_ptr<int> sp1(new int(22));
    std::shared_ptr<int> sp2 = sp1;
    std::weak_ptr<int> wp = sp1;        // 指向shared_ptr<int>所指对象

    std::cout << "count: " << wp.use_count() << std::endl;    // count: 2
    std::cout << *sp1 << std::endl;     // 22
    std::cout << *sp2 << std::endl;     // 22
    check(wp);    // still: 22

    sp1.reset();
    std::cout << "count: " << wp.use_count() << std::endl;    // count: 1
    std::cout << *sp2 << std::endl;    // 22
    check(wp);    // still: 22

    sp2.reset();
    std::cout << "count: " << wp.use_count() << std::endl;    // count: 0
    check(wp);    // still: pointer is invalid

    return;
}

int main()
{
    auto spw = std::make_shared<int>();
    std::cout<<"spw location : " << spw << std::endl;
    mytest();
    system("pause");
    std::cout<< std::endl;

    return 0;
}

3.智能指针循环引用问题及解决办法

在这里插入图片描述如图Person对象和Car对象互相引用,导致代码执行结束后两者引用计数都不为0,导致内存不能释放,出现内存泄漏。

循环引用的解决办法就是使用weak_ptr.

在这里插入图片描述右侧代码执行结束后,Person对象引用计数为1,Car对象引用计数为2;若代码执行结束后,car指针先释放,Car对象引用计数减1变为1,person指针释放后,Person对象引用计数减1变为0,Person对象释放内存,成员对象m_car也得到释放,Car对象引用指针减1变为0,Car对象也释放了内存。

参考链接:https://blog.csdn.net/weixin_45880571/article/details/119345415

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值