• C++ 智能指针


    1、new、delete是什么

    new,delete是关键字(运算符),不是函数,new和delete会调用构造函数和析构函数

    2、operator new 和operator delete

    new做了两件事,1、operator new 分配内存 2、调用构造函数来初始内存
    delete做了两件事 1、先调用析构函数 2、释放内存operator delete

    3、new[]、delete[]

    class A{};
    // 我们认为new了一个对象为2的数组应该占用2个字节,但是实际占用了6个字节
    // 2个字节表示类对象数组占用的内存,4个字节是用来记录数组的个数
    A* a = new A[2];
    delete[] a;
    // delete a;	// 会出现异常
    
    // 内置类型,使用delete和delete[]效果一样,不需要调用析构函数所以没有额外空间
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4、智能指针概述

    智能指针能自动释放指向的内存
    auto_ptr (c++98)
    unique_ptr (c++11)
    shared_ptr (c++11)
    weak_ptr (c++11)
    目前auto_ptr已经被unique_ptr取代,因为auto_ptr不安全
    这三种指针都是使用类模板
    1、shared_ptr 共享指针(多个指针指向同一块对象,最后一个指针被销毁时,这个对象会被释放)
    2、weak_ptr 是辅助 shared_ptr工作
    3、unique_ptr 独占式指针,同一个时间,只有一个指针指向该对象

    4.1、shared_ptr基础

    共享所有权,不是被一个shared_ptr拥有,而是被多个shared_ptr直接相互协助,有额外开销。
    
    工作原理:引用计数,每个shared_ptr的拷贝都是相同的内存,只有最后一个指向
    该内存(对象) 的shared_ptr指针不需要再指向该对象时,才会析构所指向的内存。
    
    1、释放时机
    a)这个shared_ptr 被析构的时候
    b)这个shared_ptr 指向别的对象时
    格式:
    	shared_ptr<指向类型名> 智能指针名
    shared_ptr<int> p1(new int(100));
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2 、使用make_shared函数

    make_shared_ptr函数:标准库里的函数模板,安全,高效的分配和使用,shared_ptr,它能够在动态分配内存中分配并初始化一个对象,然后返回shared_ptr

    shared_ptr<int> p1 = make_shared_ptr<int>(100);
    
    • 1

    3、在如下情况下,所引用的智能指针计数会增加

    auto p1 = make_shared(100);
    auto p2(p1);
    1、这种情况用p1初始化和p2
    2、把智能指针做实参传递 (如果函数形参使用的是引用,则智能指针引用计数不会增加)
    3、作为函数返回值

    4、引用计数减少

    给shared_ptr赋予新值,让他指向新的对象,会造成引用计数减少,减少到0,就会释放这个指针

    5、shared_ptr 常用操作

    5.1、use_count()

    use_count()	返回多少个智能指针指向某个对象
    示例:
    shared_ptr<int> my(new int(100);
    int count =  my.use_count();
    
    • 1
    • 2
    • 3
    • 4

    5.2、unique()

    unique():是否该智能指针独自某个对象,如果是返回true,否则返回false
    shared_ptr<int> my(new int(100);
    my.unique();
    
    • 1
    • 2
    • 3

    5.3、reset() 恢复重置

    5.3.1、reset() 不带参数

    若pi是指向该对象的唯一指针,那么会释放该指针,并将其置为空
    若pi不是指向该对象的唯一指针,指针计数会减-1,然后pi会被置空
    
    // 情况1:
    shared_ptr<int> pi(new int(100);
    pi.reset();	 // pi置为空,并且释放该内存
    if(pi==nullptr)
    	cout<<"pi为空"<<endl;
    
    // 情况2:
    shared_ptr<int> pi(new int(100);
    auto pi2(pi);
    pi.reset();	 // pi为空,引用计数-1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    5.3.2、reset() 带参数

    reset() 带参数一般是new出来的指针时
    若pi是指向该对象的唯一指针,那么会释放该指针,并将pi指向该指针
    若pi不是指向该对象的唯一指针,指针计数会减-1,然后pi指向该指针
    
    // 情况1:
    shared_ptr<int> pi(new int(100);
    pi.reset(new int(1));	 // pi指向新的指针,释放原内存
    
    // 情况2:
    shared_ptr<int> pi(new int(100);
    auto pi2(pi);
    pi.reset(new int(1));	 // 引用计数-1,pi指向新的指针
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5.3.3、reset初始化指针

    shared_ptr<int> pi;
    pi.reset(new int(10));
    
    • 1
    • 2

    5.4、*解引用

    *获得pi指向的对象

    shared_ptr<int> pi(new int(100);
    cout<<*pi<<endl; // 输出结果100
    
    • 1
    • 2

    5.5、get

    考虑到一些函数需要时裸指针,所以由此而生
    pi.get返回pi中保存的裸指针,不小心释放了,会造成严重后果

    shared_ptr<int> pi(new int(100);
    int* p = pi.get();
    
    
    • 1
    • 2
    • 3

    5.6、swap

    swap交换两个智能指针的指向

    shared_ptr<string> pi(new string("123"));
    shared_ptr<string> pi1(new string("1234"));
    pi.swap(pi1);
    
    • 1
    • 2
    • 3

    5.7、nullptr

    a)所指向的对象,引用计数减1,若引用计数为0,则释放
    shared_ptr<string> pi(new string("123"));
    p1 = nullptr;
    
    • 1
    • 2
    • 3

    5.8、智能指针作为判断条件

    shared_ptr<string> pi(new string("123"));
    if(pi)
    {
    	cout<<"指向一个对象"<<endl;
    }
    else
    {
    	cout<<"指向null"<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    5.9、指定删除器,以及数组问题

    a)指定删除器
    1、定时机帮我们删除所指向的对象,delete:将delete运算符作为默认的析构方式,我们可以指定自己的删除器,可以取代系统的默认删除器。

    void myDelete(int* p)	// 我们的删除器,当智能指针计数为零,默认调用这个函数
    {
    	if(p!=NULL)
    	{
    		delete p;
    		p = NULL;
    	}
    }
    
    // 指定删除器
    shared_ptr<int> p(new int(113),myDelete));
    
    // 删除器可以不是函数,可以时lambda表达式
    shared_ptr<int> p(new int(113),[](int* p)
    {
    	delete p;
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    默认删除器有些情况处理不了,比如动态数组(因为默认的时delete p,不是delete[] p)

    shared_ptr<int> p(new int[10],[](int* p)
    {
    	delete[] p;
    });
    
    // 可以用default_delete来做删除器,是标准的模板类
    shared_ptr<int> p(new int(113),std::default_delete<int[]>[]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    写个函数模板来封装shared_ptr数组

    template<typedef T>
    shared_ptr<T> make_shared_array(size_t size)
    {
    	return shared_ptr<T>(new T[size],default_ptr<T[]>());
    }
    // 调用
    shared_ptr<int> p = make_shared_array<int>(5);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    删除器额外说明

    两个shared_ptr指定不同的删除器,只要他们所指向的对象类型相同,那么这两个shared_ptr也属于同一个类型
    auto lambda1 = [](int* p)
    {
    	// 日志
    	delete p;
    }
    
    auto lambda2 = [](int* p)
    {
    	// 日志
    	delete p;
    }
    
    shared_ptr<int> p(new int(100),lambda1);
    shared_ptr<int> p1(new int(200),lambda2);
    p1 = p; // 先释放p1所指向的对象释放掉,然后p所指向的引用计数+1
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5.10、shared_ptr常用操作

    shared_ptr<int> func(int value)
    {
    	return make_shared<int>(value);
    }
    
    shared_ptr<int> func2()
    {
    	auto tmp = func(10);
    	return tmp;
    }
    
    auto pi = func2();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    智能指针注意事项:

    1、慎用裸指针
    不要使用裸指针初始化多个shared_ptr,会造成多次释放

    void func(shared_ptr<int> p)
    {
    
    }
    
    int* p = new p(10);
    func(shared_ptr<int>(p));	//参数是一个临时的shared_ptr,用一个裸指针显示构建
    *p = 45;	// 会出现意外错误
    // p指针已经在func函数内部释放了,因为引用计数是1,func函数的p是一个临时变量,结束时候会释放
    
    // 改善写法1
    int* p = new p(10);
    shared_ptr<int> p2(p);
    func(p2);
    *p = 11;
    
    // 改善写法2 
    shared_ptr<int> p2(new int(100));
    func(p2);
    *p = 21;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    2、慎用get返回的裸指针

    因为一些函数接口需要的是传入一个裸指针,所以就引入了get这个函数接口,get返回的指针不能delete,会导致异常,他是智能指针进行管理的。

    3、不要把类对象this指针作为shared_ptr的返回值,改用enable_shared_from_this

    class A:public enable_shared_from_this<A>
    {
    public:
    	shared_ptr<A> getself()
    	{
    		//return shared_ptr(this);	// 用裸指针初始化多个shared_ptr
    		return shared_ptr_this();	// 通过这种 方法返回智能指针
    	}
    };
    
    shared_ptr<A> pctl(new A);
    shared_ptr<A> pct2 = pctl->getself();	// 问题出现
    // 导致pctl和pct2两个智能指针没有关联
    
    // enable_shared_from_this中有一个弱指针weak_ptr,能够监视this,通过调用了lock函数,
    使强引用计数+1,并且返回shared_ptr指针
    

    4、避免循环引用

    6、weak_ptr概述

    weak_ptr是用来辅助shared_ptr使用,用来指向一个shared_ptr管理的对象,但是weak_ptr不能改变引用shared_ptr引用计数,控制不了,所指向对象的生存期,也叫弱引用,监视shared_ptr的生命周期,某种意义上说是对shared_ptr的扩充,

    6.1、weak_ptr创建

    创建weak_ptr的时候,一般使用shared_ptr来初始化,因为weak_ptr是需要依赖shared_ptr

    auto pi = shared_ptr<int>(100);
    weak_ptr<int> piw(pi);	// 弱共享,pi引用计数不改变
    
    • 1
    • 2

    6.1.1、lock

    检查weak_ptr所指向的对象是否存在,如果存在则返回shared_ptr指针,强引用计数也会加1,如果不存在,lock返回一个空shared_ptr

    auto pi = shared_ptr<int>(100);
    weak_ptr<int> piw(pi);	// 弱共享,pi引用计数不改变
    
    auto pi2 = piw.lock(); // pi2是一个shared_ptr指针
    
    • 1
    • 2
    • 3
    • 4

    6.1.2、weak_ptr常用 操作

    use_count()
    use_count()函数得到的是强引用的数量
    
    auto pi = shared_ptr<int>(100);
    weak_ptr<int> piw(pi);	// 弱共享,pi引用计数不改变
    
    piw.use_count();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    expired()
    expired()是否过期,判断所观察的资源是否已经过期
    
    auto pi = shared_ptr<int>(100);
    weak_ptr<int> piw(pi);	// 弱共享,pi引用计数不改变
    pi.reset();
    
    if(piw.expired())
    {
    	cout<<"对象过期"<<endl;
    }
    else
    {
    	cout<<"未过期"<<endl;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    reset()
    reset()将弱引用指针设置为空,不影响强引用指针数量,弱引用计数会减少1
    auto pi = shared_ptr<int>(100);
    weak_ptr<int> piw(pi);	// 弱共享,pi引用计数不改变
    
    piw.reset();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    6.2、尺寸问题

    auto pi = weak_ptr<int>(100);
    int* p;
    
    int i1 = sizeof(pi); // 8,两个裸指针
    int i2 = sizeof(p);	// 4
    
    // 第一个指针指向,智能指针指向的对象
    // 第二个指针指向,控制块
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    7、unique_ptr概述

    独占式的智能指针,同一时刻只能有一个unique_ptr指向这个对象(这块内存)

    // 初始化
    unique<int> pi(new int(100));
    
    // c++14引入了 make_unique
    auto p2 = make_unique<int>(100);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    7.1、常用操作

    1、release函数,放弃对指针的控制器(切断了和智能指针的联系,返回裸指针,将智能指针置空,裸指针可以delete手动释放,也可以来初始化其他智能指针)

    unique<int> pi(new int(100));
    unique<int> p2(pi.release());	// 利用pi返回的裸指针来初始化p2智能指针
    
    • 1
    • 2

    2、reset不带参数,释放智能指针(并且置空)
    带参数,释放智能指针指向的对象,并让该指针指针指向新对象

    unique<int> pi(new int(100));
    pi.reset();
    
    // 带参数
    pi.reset(pi.release());
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3、nullptr

    unique<int> pi(new int(100));
    pi = nullptr;	// 释放pi指向的对象,并将他指向为空
    
    • 1
    • 2

    4、指向数组
    不要忘记int[],否则释放就会出问题

    unique<int[]> pi(new int[10]);
    
    • 1

    5、get
    返回智能指针的裸指针

    unique<int> pi(new int(100));
    int* p = pi.get();
    
    • 1
    • 2

    6、*解引用
    获取智能指针指向的对象,可以指针操作

    unique<int> pi(new int(100));
    *pi = 13;
    
    • 1
    • 2

    7、swap
    交换两个智能指针所指向的对象

    unique<int> p1(new int(1001));
    unique<int> p2(new int(100));
    
    p1.swap(p2);
    
    • 1
    • 2
    • 3
    • 4

    8、转换shared_ptr
    如果unique_ptr为右值的时候就可以赋值给shared_ptr,因为shared_ptr显示构造函数,可将右值unique_ptr转换为shared_ptr,从此接管原对象

    auto func()
    {
    	return unique_ptr<int>(new int(100));
    }
    shared_ptr<int> pi = func();
    
    • 1
    • 2
    • 3
    • 4
    • 5

    9、从函数返回一个局部unique_ptr指针

    unique_ptr<int> func()
    {
    	return unique_ptr<int>(new int(100));
    }
    // 返回局部对象,这种情况会生产一个临时unique_ptr,调用unique_ptr移动构造函数
    auto pi = func();
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    10、指定删除器

    void mydelete(int* p)
    {
    	if(p!=NULL)
    	{
    		delete p;
    		p = NULL;
    	}
    }
    unique<int> pi(new int(100),mydelete);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    11 、尺寸问题

    unique<int> pi(new int(100));
    int *p;
    int r1 = sizeof(pi);
    int r2 = sizeof(p);
    
    // unique_ptr和裸指针一样都是4字节
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    8、智能指针总结

    帮助我们释放内存,防止我们忘记释放,内存泄漏。
    auto_ptr为什么被废弃:具有一部分unique_ptr,不能在容器中保存,也不能在函数中返回

  • 相关阅读:
    UE4 关卡蓝图实现开关门
    基于JAVA校园环境保护监督系统计算机毕业设计源码+系统+mysql数据库+lw文档+部署
    Nginx被它打败了?
    <蓝桥杯软件赛>零基础备赛20周--第6周--数组和队列
    Node.js中的回调地狱
    java基于RestTemplate的微服务发起http请求
    Shell-条件控制语句2
    电子商务平台系统
    七、SpringMVC(1)
    设计模式-享元模式(Flyweight)
  • 原文地址:https://blog.csdn.net/weixin_45715405/article/details/126614993