C++智能指针之weak_ptr(保姆级教学)

目录

weak_ptr-toc" style="margin-left:0px;">C++智能指针weak_ptr

概述

作用

本文涉及的所有程序

使用说明

weak_ptr%E7%9A%84%E5%B8%B8%E8%A7%84%E6%93%8D%E4%BD%9C-toc" style="margin-left:40px;">weak_ptr的常规操作

lock();

use_count();

expired();

reset();

shared_ptr%20%26%20weak_ptr-toc" style="margin-left:40px;">shared_ptr & weak_ptr

尺寸

智能指针结构框架

常见使用问题

shared_ptr%E5%A4%9A%E6%AC%A1%E5%BC%95%E7%94%A8%E5%90%8C%E4%B8%80%E6%95%B0%E6%8D%AE%EF%BC%8C%E4%BC%9A%E5%AF%BC%E8%87%B4%E4%B8%A4%E6%AC%A1%E9%87%8A%E6%94%BE%E5%90%8C%E4%B8%80%E5%86%85%E5%AD%98%EF%BC%88%E5%8F%AA%E6%B6%89%E5%8F%8Ashared_ptr%EF%BC%89-toc" style="margin-left:120px;">shared_ptr多次引用同一数据,会导致两次释放同一内存(只涉及shared_ptr

shared_ptr%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%EF%BC%88%E6%B6%89%E5%8F%8Ashared_ptr%E5%92%8Cweak_ptr%EF%BC%89-toc" style="margin-left:120px;">shared_ptr循环引用导致内存泄露(涉及shared_ptrweak_ptr

shared_ptr%E6%8C%87%E5%90%91%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E7%9A%84%E5%9C%B0%E5%9D%80%EF%BC%8C%E4%BC%9A%E5%AF%BC%E8%87%B4%E4%B8%A4%E6%AC%A1%E9%87%8A%E6%94%BE%E5%90%8C%E4%B8%80%E4%B8%AA%E5%86%85%E5%AD%98%EF%BC%88%E5%8F%AA%E6%B6%89%E5%8F%8Ashared_ptr%EF%BC%89-toc" style="margin-left:120px;">shared_ptr指向局部变量的地址,会导致两次释放同一个内存(只涉及shared_ptr

shared_ptr%E6%8E%A5%E6%94%B6shared_ptr%E6%89%80%E5%AE%9E%E4%BE%8B%E5%8C%96%E5%AF%B9%E8%B1%A1%E7%9A%84this%E6%8C%87%E9%92%88%E5%AF%BC%E8%87%B4%EF%BC%8C%E4%BC%9A%E5%AF%BC%E8%87%B4%E4%B8%A4%E6%AC%A1%E9%87%8A%E6%94%BE%E5%90%8C%E4%B8%80%E4%B8%AA%E5%86%85%E5%AD%98%EF%BC%88%E5%8F%AA%E6%B6%89%E5%8F%8Ashared_ptr%EF%BC%89-toc" style="margin-left:120px;">shared_ptr接收shared_ptr所实例化对象的this指针导致,会导致两次释放同一个内存(只涉及shared_ptr


weak_ptr">C++智能指针weak_ptr

概述

std::weak_ptr 是一种智能指针,通常不单独使用,只能和 shared_ptr 类型指针搭配使用,可以视为 shared_ptr 指针的一种辅助工具。借助 weak_ptr 类型指针可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同空间的 shared_ptr 指针、通过expired()判断shared_ptr 指针指向的堆内存是否已经被释放等等,还可以解决shared_ptr 循环引用的问题。

  • weak_ptr:类模板,弱指针(弱引用计数)
  • weak_ptr弱指针,不会控制影响对象的生命周期(不会改变对象的引用计数),shared_ptr释放指向对象时,是不会考虑weak_ptr是否指向该对象
  • weak_ptr不是独立指针,不能单独操作所指向的资源(不配拥有对象),更不能指向一个新的空间;

作用

本文涉及的所有程序

00_code.cpp

#include <iostream>
#include <memory>

using namespace std;

class A
{
public:
    A()
    {
        cout << "A" << endl;
    }

    A(int num) : m_num(num)
    {
        cout << "A int" << endl;
    }

    A(const A&& other) : m_num(other.m_num)
    {
        cout << "A move int" << endl;
    }

    ~A()
    {
        cout << "~A" << endl;
    }

public:
    int m_num;
};

int main(int argc, char const* argv[])
{
    shared_ptr<A> pa(new A(5));
    weak_ptr<A> wpa = pa;
    weak_ptr<A> wpa2 = pa;
    weak_ptr<A> wpa3 = pa;

    // weak_ptr常用功能
    auto pb = wpa.lock();
    if (pb == nullptr)
    {
        cout << "pb is nullptr" << endl;
    }

    // use_count();返回的是shared_ptr的引用计数
    cout << wpa.use_count() << endl;

    // expired():判断当前弱指针指向的对象是否被释放
    if (wpa.expired())
    {
        cout << "wpa pointer class is free" << endl;
    }

    wpa.reset();
    return 0;
}

01_code.cpp

#include <iostream>
#include <memory>

using namespace std;

class Child;

class Parent
{
public:
    Parent()
    {
        cout << "Parent" << endl;
    }
    ~Parent()
    {
        cout << "~Parent" << endl;
    }
    //shared_ptr<Child> c;
    weak_ptr<Child> c;
};

class Child
{
public:
    Child()
    {
        cout << "Child" << endl;
    }
    ~Child()
    {
        cout << "~Child" << endl;
    }
    
    shared_ptr<Parent> p;
};

int main(int argc, char const* argv[])
{
    shared_ptr<Parent>pp(new Parent());//pp:1
    shared_ptr<Child>cc(new Child());//cc:1

    //循环引用
    pp->c = cc;//cc:1 pp:1
    cc->p = pp;//pp:2 cc:1

    cout << pp.use_count()<<endl;
    cout << cc.use_count() << endl;

    return 0;
}

02_code.cpp

#include <iostream>
#include <memory>

using namespace std;

int main(int argc, char const *argv[])
{
    shared_ptr<int> p(new int(5));
    weak_ptr<int> wp = p;

    //shared_ptr/weak_ptr的尺寸大小是裸指针的两倍
    cout << sizeof(int *) << endl;
    cout << sizeof(p) << endl;
    cout << sizeof(wp) << endl;

    return 0;
}

03_code.cpp

#include <iostream>
#include <memory>

using namespace std;

class A:public enable_shared_from_this<A>
{
public:
    A()
    {
        cout << "A" << endl;
    }

    A(int num) : m_num(num)
    {
        cout << "A int" << endl;
    }

    A(const A &&other) : m_num(other.m_num)
    {
        cout << "A move int" << endl;
    }
    shared_ptr<A> getAddr()
    {
        //return shared_ptr<A>(this);
        return shared_from_this();//返回可共享的this指针
    }

    ~A()
    {
        cout << "~A" << endl;
    }

public:
    int m_num;
};

int main(int argc, char const *argv[])
{
    // A a;
    // shared_ptr<A>temp(&a);
    // shared_ptr<A> temp = a.getAddr();

    // int num = 5;
    // shared_ptr<int>p(&num);

    //shared_ptr<A> pa(new A());
    A *pa = new A();
    shared_ptr<A> temp = pa->getAddr();
    return 0;
}

使用说明

在VS2022中进行调试,执行完第一条语句后,pa的强引用计数加1

执行完第二句的弱指针赋值后,发现多了一个弱引用计数,和强引用计数一样都为1

增加pa.reset()的操作。通过调试可以发现:不关心是否有弱指针指向当前对象,只要指向当前的指针强引用计数为0了,当前对象就会调用析构函数释放空间。

weak_ptr无法指向一个新的空间(只能指向已有的智能指针),它不配拥有一个对象,只能作为一个指向

weak_ptr不可以直接赋值给shared_ptr

weak_ptr%E7%9A%84%E5%B8%B8%E8%A7%84%E6%93%8D%E4%BD%9C">weak_ptr的常规操作

lock();

获取弱指针指向的对象对应的共享指针,如果指向的对象释放,那么返回一个nullptr

调用lock函数来获得shared_ptr(如果对象已经被释放,则返回一个空的shared_ptr

(有些书上叫做将弱指针转换为共享指针)

在VS2022下调试结果如下:

在调用lock前,pa的强引用计数为1

在调用lock后,pa的强引用计数变为2

use_count();

功能:返回有多少个shared_ptr智能指针指向某对象;(引用计数的个数)

用途:主要用于调试

expired();

判断弱指针是否过期(所指向的对象是否被释放true/false)

reset();

将该弱指针设置为空,弱引用计数减1,强引用计数不变

执行wpa.reset前,弱引用计数为3,强引用计数为2

执行wpa.reset后,弱引用计数减1,变为2;强引用计数仍为2

shared_ptr%20%26%20weak_ptr">shared_ptr & weak_ptr

尺寸

shared_ptrweak_ptr一样大,是裸指针的两倍;

智能指针结构框架

从中可以发现智能指针实际上由两个指针组成:一个指针指向数据,一个指针指向控制块

常见使用问题

shared_ptr%E5%A4%9A%E6%AC%A1%E5%BC%95%E7%94%A8%E5%90%8C%E4%B8%80%E6%95%B0%E6%8D%AE%EF%BC%8C%E4%BC%9A%E5%AF%BC%E8%87%B4%E4%B8%A4%E6%AC%A1%E9%87%8A%E6%94%BE%E5%90%8C%E4%B8%80%E5%86%85%E5%AD%98%EF%BC%88%E5%8F%AA%E6%B6%89%E5%8F%8Ashared_ptr%EF%BC%89">shared_ptr多次引用同一数据,会导致两次释放同一内存(只涉及shared_ptr

int* pInt = new int[100];

shared_ptr sp1(pInt);

// 一些其它代码之后…

shared_ptr sp2(pInt);

shared_ptr%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8%E5%AF%BC%E8%87%B4%E5%86%85%E5%AD%98%E6%B3%84%E9%9C%B2%EF%BC%88%E6%B6%89%E5%8F%8Ashared_ptr%E5%92%8Cweak_ptr%EF%BC%89">shared_ptr循环引用导致内存泄露(涉及shared_ptrweak_ptr

我们定义了两个类:Parent和Child,两个类没有继承关系;在Parent中定义了一个Child的智能指针,在Child中定义了一个指向Parent类型的智能指针

在main函数中,定义分别定义Parent和Child类型的指针,让它们内部的指针互相指向

这样就产生了循环引用的现象

编译报错,这是由于未前置声明Child类,Parent类中找不到Child

加上前置声明

重新编译运行结果如下:发现两个类只构造了,没有析构释放,导致了内存泄漏

通过VS2022调试可以发现,两个main中的智能指针在循环引用后,引用计数都变成了2。在程序运行结束时,main中的两个智能指针释放了之后,引用计数减1后变为1,大于0;而两个在类中定义的智能指针,由于它们属于类中的属性,它们必须在析构函数被调用了才能释放,而程序结束引用计数不为0,也就无法调用析构函数。因此这样就导致了内存泄漏。

以图示说明如下:

解决方法:我们将类中的两个指针随便一个改为weak_ptr

如图,我修改的是Parent中的指针,运行发现两个对象空间可以被正常释放

分析:由于Parent类中的是weak_ptr,因此执行完p->c = cc;cc->p = pp;后,cc的强引用计数不变,仍为1,pp的强引用计数为2;当main中的return 0;执行完之后,局部变量释放,pp引用计数变成1,cc引用计数变为0,从而会调用Child的析构函数,将Child类中的shared_ptrp释放,因此pp的引用计数也变为0,最终调用Parent的析构函数,将全部空间释放掉。

shared_ptr%E6%8C%87%E5%90%91%E5%B1%80%E9%83%A8%E5%8F%98%E9%87%8F%E7%9A%84%E5%9C%B0%E5%9D%80%EF%BC%8C%E4%BC%9A%E5%AF%BC%E8%87%B4%E4%B8%A4%E6%AC%A1%E9%87%8A%E6%94%BE%E5%90%8C%E4%B8%80%E4%B8%AA%E5%86%85%E5%AD%98%EF%BC%88%E5%8F%AA%E6%B6%89%E5%8F%8Ashared_ptr%EF%BC%89">shared_ptr指向局部变量的地址,会导致两次释放同一个内存(只涉及shared_ptr

我们在类中定义了一个函数,用于返回当前对象的地址,其中this指针使用shared_ptr进行包装。

在main中实例化一个对象,并用一个智能指针来获取对象地址。

发现报错:段错误,局部对象被释放了两次

这是由于a是局部对象,它在程序运行结束的时候会自己调用析构函数进行释放,而temp是指向这个局部变量的智能指针,它在程序结束的时候会再次释放局部变量,因此导致了空间被释放两次,产生了段错误。与下图情况一模一样

同样使用智能指针接收对象的this指针也不行

解决方法:

通过裸指针申请空间的方法,实例化对象,然后再用智能指针接收对象返回值

shared_ptr%E6%8E%A5%E6%94%B6shared_ptr%E6%89%80%E5%AE%9E%E4%BE%8B%E5%8C%96%E5%AF%B9%E8%B1%A1%E7%9A%84this%E6%8C%87%E9%92%88%E5%AF%BC%E8%87%B4%EF%BC%8C%E4%BC%9A%E5%AF%BC%E8%87%B4%E4%B8%A4%E6%AC%A1%E9%87%8A%E6%94%BE%E5%90%8C%E4%B8%80%E4%B8%AA%E5%86%85%E5%AD%98%EF%BC%88%E5%8F%AA%E6%B6%89%E5%8F%8Ashared_ptr%EF%BC%89">shared_ptr接收shared_ptr所实例化对象的this指针导致,会导致两次释放同一个内存(只涉及shared_ptr

继续以上面的class A为例,通过智能指针实例化从堆区new出来的对象,通过智能指针接收对象的this指针,也会导致空间被释放两次

解决方法:

针对通过智能指针实例化从堆区new出来的对象,通过智能指针接收对象的地址。而对于任何局部变量此方法无效(我们也可以使用上面的方法,直接使用裸指针从堆区实例化对象)

我们需要继承一个模板类enable_shared_from_this,并将要返回的this指针改为shared_from_this(),此方法可以返回可共享的this指针

运行结果:


http://www.niftyadmin.cn/n/4998662.html

相关文章

<深度学习基础> Batch Normalization

Batch Normalization批归一化 BN优点 减少了人为选择参数。在某些情况下可以取消dropout和L2正则项参数&#xff0c;或者采取更小的L2正则项约束参数&#xff1b;减少了对学习率的要求。现在我们可以使用初始很大的学习率或者选择了较小的学习率&#xff0c;算法也能够快速训…

「Docker」M1 Pro 打包docker image问题合集

运行docker 遇到 The requested images platform (linux/arm64/v8) does not match the detected host platform (linux/amd64/v4) and no specific platform was requested 说明打包的镜像没有 linux/amd64 解决方案&#xff1a;重新打包镜像 docker buildx build --platfor…

介绍OpenCV

OpenCV是一个开源计算机视觉库&#xff0c;可用于各种任务&#xff0c;如物体识别、人脸识别、运动跟踪、图像处理和视频处理等。它最初由英特尔公司开发&#xff0c;目前由跨学科开发人员社区维护和支持。OpenCV可以在多个平台上运行&#xff0c;包括Windows、Linux、Android和…

技术面试与HR面:两者之间的关联与区别

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

横向对比 npm、pnpm、tnpm、yarn 优缺点

前端工程化是现代Web开发中不可或缺的一环&#xff0c;它的出现极大地提升了前端开发的效率和质量。 在过去&#xff0c;前端开发依赖于手动管理文件和依赖&#xff0c;这导致了许多问题&#xff0c;如版本冲突、依赖混乱和构建繁琐等。而今&#xff0c;随着众多前端工程化工具…

list【2】模拟实现(含迭代器实现超详解哦)

模拟实现list 引言&#xff08;实现概述&#xff09;list迭代器实现默认成员函数operator* 与 operator->operator 与 operator--operator 与 operator!迭代器实现概览 list主要接口实现默认成员函数构造函数析构函数赋值重载 迭代器容量元素访问数据修改inserterasepush_ba…

2023开学礼《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书南京师范大学图书馆

2023开学礼《乡村振兴战略下传统村落文化旅游设计》许少辉八一新书南京师范大学图书馆

关于Comparable、Comparator接口返回值决定顺序的问题

Comparable和Comparator接口都是实现集合中元素的比较、排序的&#xff0c;下面先简单介绍下他们的用法。 1. 使用示例 public class Person {private String name;private Integer age;public Person() {}public Person(String name, Integer age) {this.name name;this.ag…