首页 > 编程语言 >C++11实用特性3 --智能指针

C++11实用特性3 --智能指针

时间:2023-08-11 15:57:17浏览次数:64  
标签:11 初始化 -- C++ 智能 内存 shared ptr 指针

1 智能指针

在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。解决这个问题最有效的方法是使用智能指针(smart pointer)。智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件<memory>

  • std::shared_ptr:共享的智能指针
  • std::unique_ptr:独占的智能指针
  • std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr的。

shared和unique的区别:

使用共享的智能指针可以有多个shared_ptr指向同一块内存,每一个智能指针内部都有计数,指向的指针每多一个,内部计数就会加1,析构一个就减1;当计数值为1时,这个时候再析构时,指针会先析构掉这片内存,就像我们调用delete一样,然后再调用自己的析构函数把自己析构掉

如果使用独享的指针unique_ptr,一块内存只能由一个unique_ptr进行管理,如果有其它的unique想要管理,就只能用move进行资源转移。它的内部计数不能超过1.

2 shared_ptr

1 初始化

初始化方式一共有4种

  1. 通过构造函数初始化
// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);

// 使用智能指针管理一块 int 型的堆内存
shared_ptr<int> ptr1(new int(520));

// 使用智能指针管理一块字符数组对应的堆内存
shared_ptr<char> ptr2(new char[12]);

// 创建智能指针对象, 不管理任何内存
shared_ptr<int> ptr3;

// 创建智能指针对象, 初始化为空
shared_ptr<int> ptr4(nullptr);

 
2. 通过拷贝和移动构造函数初始化
当一个智能指针被初始化之后,就可以通过这个智能指针初始化其他新对象。在创建新对象的时候,对应的拷贝构造函数或者移动构造函数就被自动调用了。

// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
shared_ptr<int> ptr1(new int(520));

//调用拷贝构造函数
shared_ptr<int> ptr2(ptr1);
shared_ptr<int> ptr3 = ptr1;

//调用移动构造函数
shared_ptr<int> ptr4(std::move(ptr1));
std::shared_ptr<int> ptr5 = std::move(ptr2);

如果使用拷贝的方式初始化共享智能指针对象,这两个对象会同时管理同一块堆内存,堆内存对应的引用计数也会增加;如果使用移动的方式初始智能指针对象,只是转让了内存的所有权,管理内存的对象并不会增加,因此内存的引用计数不会变化。

 
3.通过std::make_shared初始化(主要方法)
通过C++提供的std::make_shared() 就可以完成内存对象的创建并将其初始化给智能指针,函数原型如下:

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
  • T:模板参数的数据类型
  • Args&&... args :要初始化的数据,如果是通过make_shared创建对象,需按照构造函数的参数列表指定

使用示例:

class Test
{
public:
    Test() 
    {
        cout << "construct Test..." << endl;
    }
    Test(int x) 
    {
        cout << "construct Test, x = " << x << endl;
    }
    Test(string str) 
    {
        cout << "construct Test, str = " << str << endl;
    }
    ~Test()
    {
        cout << "destruct Test ..." << endl;
    }
};

int main()
{
    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1 = make_shared<int>(520);
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;

    shared_ptr<Test> ptr2 = make_shared<Test>();
    cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;

    shared_ptr<Test> ptr3 = make_shared<Test>(520);
    cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;

    shared_ptr<Test> ptr4 = make_shared<Test>("我是要成为海贼王的男人!!!");
    cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
    return 0;
}

使用std::make_shared()模板函数可以完成内存地址的创建,并将最终得到的内存地址传递给共享智能指针对象管理。如果申请的内存是普通类型,通过函数的()可完成地址的初始化,如果要创建一个类对象,函数的()内部需要指定构造对象需要的参数,也就是类构造函数的参数。

4.通过reset方法初始化
对于一个未初始化的共享智能指针,可以通过reset方法来初始化,当智能指针中有值的时候,调用reset会使引用计数减1。

2 使用

就像对象指针一样使用即可,如果初始化智能指针的时候用类,那么可以用->来调用对象的成员函数。或者可以用.来调用属于智能指针的方法,比如get()

3 指定删除器

当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。

#include <iostream>
#include <memory>
using namespace std;

// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p)
{
    delete p;
    cout << "int 型内存被释放了...";
}

int main()
{
    shared_ptr<int> ptr(new int(250), deleteIntPtr);
    return 0;
}

在C++11中使用shared_ptr管理动态数组时,需要指定删除器,因为std::shared_ptr的默认删除器不支持数组对象,具体的处理代码如下:

int main()
{
    shared_ptr<int> ptr(new int[10], [](int* p) {delete[]p; });
    return 0;
}

在删除数组内存时,除了自己编写删除器,也可以使用C++提供的std::default_delete()函数作为删除器,这个函数内部的删除功能也是通过调用delete来实现的,要释放什么类型的内存就将模板类型T指定为什么类型即可。具体处理代码如下:

shared_ptr<int> ptr(new int[10], default_delete<int[]>());

3 weak_ptr

弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的内存,不管有多少shared_ptr,我们始终关注的都是那片内存空间的使用情况

1 初始化

// 默认构造函数
constexpr weak_ptr() noexcept;
// 拷贝构造
weak_ptr (const weak_ptr& x) noexcept;
template <class U> weak_ptr (const weak_ptr<U>& x) noexcept;
// 通过shared_ptr对象构造
template <class U> weak_ptr (const shared_ptr<U>& x) noexcept;

    shared_ptr<int> sp(new int);

    weak_ptr<int> wp1;
    weak_ptr<int> wp2(wp1);
    weak_ptr<int> wp3(sp);
    weak_ptr<int> wp4;
    wp4 = sp;
    weak_ptr<int> wp5;
    wp5 = wp3;

2 常用方法

  • use_count()方法可以获得当前所观测资源的引用计数
  • expired()方法来判断观测的资源是否已经被释放
  • lock()方法来获取管理所监测资源的shared_ptr对象
  • reset()方法来清空对象,使其不监测任何资源

3 shared_ptr的使用注意事项:

  1. 不能用一个原始地址(new出来的那个地址)初始化多个共享智能指针,因为两个智能指针对象互相不知道对方的存在,各自的引用计数都为1,释放的时候就会造成重复析构,共享智能指针的计数作用是用一个智能指针去初始化另一个才知道的
  2. 函数不能返回管理了this的共享智能指针对象,本质和第一条一样
  3. 共享智能指针不能循环引用,指两个智能指针对象不能互相存储,解决方式就是用shared_ptr

标签:11,初始化,--,C++,智能,内存,shared,ptr,指针
From: https://www.cnblogs.com/dreamer-q/p/17620812.html

相关文章

  • WPF的前世今生
    1、WPF的布局WPF的布局分为相对定位和绝对定位两种。绝对定位一般用Canvas相对定位一般用Grid、StackPanel、DockPanel、WrapPanel2、MVVM模式是什么MVVM就是ModelViewViewModelModel层就是数据层,你可以把它理解成实体层,一般情况下,只要不和服务端挂钩或者小型项目,写一个实......
  • 经纬恒润第三代重载自动驾驶运输车批量交付唐山港客户
        2023年7月下旬,唐山港京唐港区集装箱码头25#泊位智能水平运输项目的关键组成部分——22辆重载自动驾驶运输车(HAV)陆续分批交付,这标志着该项目迈入了全面系统联调联测阶段。    此次交付的经纬恒润自研第三代HAV车型,在底盘动力性、经济性、整车电子电气架构、通信......
  • Linux shell 脚本常用指南
    常用语法变量#!/bin/bash#字符串USER_NAME="shell"#数字AGE=25echo${USER_NAME}echo${AGE}数组#!/bin/bash#普通数组定义USER_IDS=(1234)echo${USER_IDS[0]}echo${USER_IDS[1]}echo${USER_IDS[2]}echo${USER_IDS[3]}USER_IDS[0]=-1echo${USER_IDS[......
  • IOS App内嵌H5 swiper 轮播出现卡顿白屏闪烁
    话说在前头:前端开发同学遇到这个问题不慌,因为接下来你要踩的坑我都帮你们踩完了,所以有了这一篇博客。希望能帮到你轮播组件:[email protected](4x稳定的最后一个版本)设备:ios版本15x,14和16版本都挺好的,问题仅在ios15的版本出现现象:左右滑动卡顿,不连手,放开的......
  • 百度人脸识别授权序列号-设备时间复原问题
    Q:为什么单设备授权序列号失效?A:以下情况都有可能导致序列号失效,请您进行一-排查1.测试序列号过期,请在百度智能云管理后台申请更多测试序列号2.CPU、网卡等硬件损坏导致硬件指纹变更3.已经激活的设备硬件变更4.SDK升级:安卓1.0&2.0版本升级至3.0&4.0&5.0版本会导......
  • MySQL学习总结
    知者不言,言者不知。1、SQL命令总览可以把SQL分为两个部分:数据操作语言(DML)和数据定义语言(DDL)。(1)数据操作语言(DML)主要是针对表的操作:INSERTINTO-向数据库表中插入数据(增)DELETE-从数据库表中删除数据(删)SELECT-从数据库表中获取数据(查)UPDATE-更新数......
  • Linux_CentOS
    windows和macOS是个人桌面操作系统;linux是服务器操作系统一、入门Linux1.1操作系统概述操作系统调度和管理计算机硬件进行工作,调度CPU,内存,硬盘,网卡,音响等发消息-->操作系统-->网卡驱动-->网卡发送数据包-->服务器-->接收计算机由硬件和软件组成,操作系统是软件的一类,主要协助......
  • Mybatis
    MybatisMyBatis是一款优秀的持久层框架,用于简化JDBC的开发。官网:https://mybatis.org/mybatis-3/zh/index.html入门使用Mybatis操作数据库,就是在Mybatis中编写SQL查询代码,发送给数据库执行,数据库执行后返回结果。Mybatis操作数据库的步骤:1.准备工作(创建springboot工程、......
  • python优雅地爬虫
    我需要获得新闻,然后tts,在每天上班的路上可以听一下。具体的方案后期我也会做一次分享。先看我喜欢的万能的老路:获得html内容->python的工具库解析,获得元素中的内容,完成。好家伙,我知道我爬取失败了。一堆js代码,看得我烦。我一去看页面发现:原来新闻的获得是走的接口,然后js插......
  • 封装一个useTable 内置分页 条件变换查询
    import{Table}from'antd';import{useImmer}from'common/hooks/useImmer';import{get}from'utils/request';importtype{ColumnsType,TablePaginationConfig}from'antd/es/table';import{useState}from......