C++11 智能指针学习笔记

非常棒的学习博客

在C++中没有垃圾回收机制,必须自己释放分配的内存,否则就会造成内存泄露。

1. shared_ptr

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针 shared_ptr 是一个模板类。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

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

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

共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数 use_count()

初始化

构造函数初始化
// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);shared_ptr<int> ptr1(new int(123)); // 管理一个 int 型数据对应的堆内存
shared_ptr<char> ptr2(new char[12]);  // 管理字符数组对应的堆内存int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);// error, 编译不会报错, 运行会出错,不要使用一个原始指针初始化多个shared_ptr。
拷贝和移动构造函数初始化
shared_ptr<int> ptr1(new int(520));  // 构造函数初始化
shared_ptr<int> ptr2(ptr1);	  // 拷贝构造函数
shared_ptr<int> ptr3 = ptr1;  // 拷贝赋值函数
shared_ptr<int> ptr4(std::move(ptr1));  // 移动构造函数,转移所有权,此时 ptr1指针置空
shared_ptr<int> ptr5 = std::move(ptr2); // 移动赋值函数,转移所有权,此时 ptr2指针置空
  1. 使用move不会使内存引用计数增加,作为move参数的 p 指针会被置空
  2. 使用拷贝构造函数新增的指针会使内存引用计数增加
  3. 作为参数传递给一个函数以及作为函数的返回值时,内存计数器也会增加
std::make_shared 初始化(最安全的分配和使用动态内存的方法)

make_shared 也定义在头文件 memory

// 例子
shared_ptr<int> ptr1 = make_shared<int>(520);
reset 方法初始化
shared_ptr<int> ptr;
ptr.reset(new int(123)); // ptr管理一个整数数据 
ptr.reset();  // 内存释放,ptr指针置空

获取原始指针

调用共享智能指针类提供的 get() 方法可以获取原始内存的地址

shared_ptr<int> ptr1 = make_shared<int>(33);
int *ptr2 = ptr1.get();

指定删除器

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

// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p){delete p;cout << "int 型内存被释放了...";
}
void test() {shared_ptr<int> ptr(new int(250), deleteIntPtr);shared_ptr<int> ptr(new int(250), [](int* p) {delete p; }); // 使用lambda表达式也可以
}

函数的参数就是智能指针管理的内存的地址,有了这个地址之后函数体内部就可以完成删除操作了。

删除动态数组

shared_ptr<int> ptr(new int[10], default_delete<int[]>()); 		// c++提供的删除函数
shared_ptr<int> ptr(new int[10], [](int* p) {delete[] p; }); 	// lambda表达式

可以自己封装一个 make_shared_array 方法来让 shared_ptr 支持数组

template <typename T>
shared_ptr<T> make_shared_array(size_t size)
{// 返回匿名对象return shared_ptr<T>(new T[size], default_delete<T[]>());
}
void test() {shared_ptr<int> ptr1 = make_shared_array<int>(10);cout << ptr1.use_count() << endl;
}

什么是 size_t
size_t 是一种无符号的整型数,它的取值没有负数,在数组中也用不到负数,而它的取值范围是整型数的双倍。sizeof操作符的结果类型是 size_t ,它在头文件中 typedef 为 unsigned int 类型。

typedef unsigned int size_t

2. unique_ptr

初始化

std::unique_ptr 是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个 unique_ptr 赋值给另一个unique_ptr

// 通过构造函数初始化对象
unique_ptr<int> ptr1(new int(10));
unique_ptr<int> ptr2 = ptr1; // error, 不允许将一个unique_ptr赋值给另一个unique_ptr//  转移所有权,函数返回值赋值
unique_ptr<int> func()
{return unique_ptr<int>(new int(520));
}
void test() {unique_ptr<int> ptr2 = move(ptr1);  // 转移所有权,此时 ptr1指针置空unique_ptr<int> ptr3 = func();  // 通过函数返回给其他的 unique_ptr
}// reset
// 使用reset方法可以让unique_ptr解除对原始内存的管理,也可以用来初始化一个独占的智能指针。
unique_ptr<int> ptr1;
ptr1.reset(new int(10));
ptr1.reset(); // ptr1指针置空

get()方法

shared_ptr 相同

删除器

unique_ptr 指定删除器和 shared_ptr 指定删除器是有区别的,unique_ptr 指定删除器的时候需要确定删除器的类型,所以不能像 shared_ptr 那样直接指定删除器。

实例:

shared_ptr<int> ptr1(new int(10), [](int*p) {delete p; });	// ok
unique_ptr<int> ptr1(new int(10), [](int*p) {delete p; });	// error// 使用函数指针
using func_ptr = void(*)(int*);
unique_ptr<int, func_ptr> ptr1(new int(10), [](int*p) {delete p; });

在上面的代码中第7行,func_ptr 的类型和 lambda表达式 的类型是一致的。
在lambda表达式没有捕获任何变量的情况下是正确的,但是如果捕获了变量,编译时则会报错:

using func_ptr = void(*)(int*);
unique_ptr<int, func_ptr> ptr1(new int(10), [&](int*p) {delete p; });	// error

上面的代码中错误原因是这样的,在lambda表达式没有捕获任何外部变量时,可以直接转换为函数指针,一旦捕获了就无法转换了,如果想要让编译器成功通过编译,那么需要使用可调用对象包装器来处理声明的函数指针:

#include <functional> // function 的头文件
unique_ptr<int, function<void(int*)>> ptr1(new int(10), [&](int*p) {delete p; });

weak_ptr

弱引用智能指针 std::weak_ptr 可以看做是 shared_ptr 的助手,它不管理 shared_ptr 内部的指针。std::weak_ptr 没有重载操作符 *->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视 shared_ptr 中管理的资源是否存在。

shared_ptr<int> sp(new int);weak_ptr<int> wp1;  	 // 构造了一个空weak_ptr对象
weak_ptr<int> wp2(wp1);	 //  拷贝构造
weak_ptr<int> wp3(sp); 	 // 通过一个shared_ptr对象构造了一个可用的weak_ptr实例对象
weak_ptr<int> wp4;		
wp4 = sp;				// wp4监视sp(隐式类型转换)
weak_ptr<int> wp5;
wp5 = wp3;				// 赋值

use_count 方法

通过调用 std::weak_ptr类提供的use_count()方法可以获得当前所观测资源的引用计数

上述代码中,wp3wp4wp5监测的资源是同一个

expired 方法

通过调用std::weak_ptr类提供的expired()方法来判断所观测的资源(shared_ptr指向的内存)是否已经被释放。若 w.use_count() 为 0,返回 true,否则返回 false
shared_ptr 指针可以通过 reset() 释放资源

lock 方法

通过调用std::weak_ptr 类提供的 lock() 方法来获取所监测的shared_ptr对象

reset 方法

通过调用std::weak_ptr类提供的reset()方法来放弃监视对象,使其不监测任何资源。
因为不再监控任何资源,因此

返回管理this的shared_ptr

C++11中为我们提供了一个模板类叫做std::enable_shared_from_this<T>,这个类中有一个方法叫做shared_from_this(),通过这个方法可以返回一个共享智能指针。在函数的内部就是使用weak_ptr来监测this对象,并通过调用weak_ptrlock()方法返回一个shared_ptr对象。

class Test : public enable_shared_from_this<Test>
{
public:shared_ptr<Test> getSharedPtr(){return shared_from_this();}//   shared_ptr<Test> getSharedPtr() {return shared_ptr<Test>(this);}// 直接返回this指针会导致通过getSharedPtr()初始化的shared_ptr的引用计数不会增加,最后被对象被析构两次的错误~Test(){cout << "class Test is disstruct ..." << endl;}
};
int main() 
{shared_ptr<Test> sp1(new Test); // 必须先对 sp1初始化,保证weak_ptr具有检测对象,才能调用下边的getSharedPtr()cout << "use_count: " << sp1.use_count() << endl;shared_ptr<Test> sp2 = sp1->getSharedPtr();cout << "use_count: " << sp1.use_count() << endl;return 0;
}

解决循环引用问题

在这里插入图片描述

ap 和 aptr 是指向类TA的共享指针,bp 和 bptr 是指向类TB的贡献指针

红色箭头表示共享指针指向申请的空间,蓝色箭头是对象里的智能指针指向内存空间

共享智能指针ap、bp对TA、TB实例对象的引用计数变为2,在共享智能指针离开作用域之后引用计数只能减为1,这种情况下不会去删除智能指针管理的内存,导致类TA、TB的实例对象不能被析构,最终造成内存泄露。

通过使用weak_ptr可以解决这个问题,只要将类TA或者TB的任意一个成员改为weak_ptr,修改之后的代码如下:

struct TA;
struct TB; // 声明类struct TA{weak_ptr<TB> bptr; // 将其中一个类里的shared_ptr改成weak_ptr即可解决~TA(){cout << "class TA is disstruct ..." << endl;}
};
struct TB{shared_ptr<TA> aptr;~TB(){cout << "class TB is disstruct ..." << endl;}
};
void testPtr(){shared_ptr<TA> ap(new TA);shared_ptr<TB> bp(new TB);cout << "TA object use_count: " << ap.use_count() << endl;cout << "TB object use_count: " << bp.use_count() << endl;ap->bptr = bp;bp->aptr = ap;cout << "TA object use_count: " << ap.use_count() << endl;cout << "TB object use_count: " << bp.use_count() << endl;
}

上面程序中,在对类TA成员赋值时ap->bptr = bp;由于bptrweak_ptr类型,这个赋值操作并不会增加引用计数,所以bp的引用计数仍然为1,在离开作用域之后bp的引用计数减为0,类TB的实例对象被析构。

在类TB的实例对象被析构的时候,内部的aptr也被析构,其对TA对象的管理解除,内存的引用计数减为1,当共享智能指针ap离开作用域之后,对TA对象的管理也解除了,内存的引用计数减为0,类TA的实例对象被析构。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/161679.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Framebuffer 介绍和应用编程

前言&#xff1a; 使用的开发板为韦东山老师的 IMX6ULL 目录 Framebuffer介绍 LCD 操作原理 涉及的 API 函数 1.open 函数 2.ioctl 函数 3.mmap 函数 Framebuffer 程序分析 1.打开设备 2.获取 LCD 参数 3.映射 Framebuffer 4.描点函数 5.随便画几个点 6.上机实验…

[vmware]vmware虚拟机压缩空间清理空间

vmware中的ubuntu使用如果拷贝文件进去在删除&#xff0c;vmare镜像文件并不会减少日积月累会不断是的真实物理磁盘空间大幅度减少&#xff0c;比如我以前windows操作系统本来只有30GB最后居然占道硬盘200GB&#xff0c;清理方法有2种。 第一种&#xff1a;vmware界面操作 第二…

Lamport Clock算法

Lamport Clock 是一种表达逻辑时间的逻辑时钟&#xff08;logical clock&#xff09;&#xff0c;能够计算得到历史事件的时间偏序关系。 假设 P0进程是分布式集群中心节点中的监控者&#xff0c;用于统一管理分布式系统中事件的顺序。其他进程在发送消息之前和接受事件消息之后…

持续进化,快速转录,Faster-Whisper对视频进行双语字幕转录实践(Python3.10)

Faster-Whisper是Whisper开源后的第三方进化版本&#xff0c;它对原始的 Whisper 模型结构进行了改进和优化。这包括减少模型的层数、减少参数量、简化模型结构等&#xff0c;从而减少了计算量和内存消耗&#xff0c;提高了推理速度&#xff0c;与此同时&#xff0c;Faster-Whi…

包装印刷行业万界星空科技云MES解决方案

印刷业的机械化程度在国内制造行业内算是比较高的&#xff0c;不算是劳动密集型企业。如书本的装订、包装的模切、烫金、糊盒等都已经有了全自动设备。印刷厂除了部分手工必须采用人工外&#xff0c;大部分都可以采用机器&#xff0c;也就意味着可以由少量工人生产出大量产品。…

【油猴脚本】学习笔记

目录 新建用户脚本模板源注释 测试代码获取图标 Tampermonkey v4.19.0 原教程&#xff1a;手写油猴脚本&#xff0c;几分钟学会新技能——王子周棋洛   Tampermonkey首页   面向 Web 开发者的文档   Greasy Fork 新建用户脚本 打开【管理面板】 点击【】&#xff0c;即…

python之range 函数

文章目录 range() 函数的语法参数说明range() 返回值使用示例&#xff1a;示例 1&#xff1a;简单使用示例 2&#xff1a;设置起始值、结束值和步长 注意事项&#xff1a; range() 是一个内置的 Python 函数&#xff0c;通常用于创建一个表示一系列数字的不可变的序列&#xff…

单链表的建立(头插法、尾插法)(数据结构与算法)

如果要把很多个数据元素存到一个单链表中&#xff0c;如何操作&#xff1f; 1.初始化一个单链表 2. 每次取一个数据元素&#xff0c;插入到表尾/表头 1. 尾插法建立单链表 尾插法建立的单链表元素顺序与输入数据集合的顺序相同&#xff0c;即按照输入数据的顺序排列。 使用尾插…

Java连接Redis并操作Redis中的常见数据类型

目录 一. Java连接Redis 1. 导入依赖 2. 建立连接 二. Java操作Redis的常见数据类型存储 1. Redis字符串(String) 2. Redis哈希(Hash) 3. Redis列表&#xff08;List&#xff09; 4. Redis集合&#xff08;Set&#xff09; 一. Java连接Redis 1. 导入依赖 pom依赖…

爬虫项目-爬取股吧(东方财富)评论

1.最近帮别人爬取了东方财富股吧的帖子和评论&#xff0c;网址如下&#xff1a;http://mguba.eastmoney.com/mguba/list/zssh000300 2.爬取字段如下所示&#xff1a; 3.爬虫的大致思路如下&#xff1a;客户要求爬取评论数大于5的帖子&#xff0c;首先获取帖子链接&#xff0c…

以太网【FPGA】

1物理&#xff1a; 2接线&#xff1a; 信号名 信号类型 对应引脚 备注 sys_clk Input B5 系统晶振输入时钟&#xff0c;频率 50MHz sys_rst_n Input E8 系统复位信号&#xff0c;低有效 eth_rxc Input E17 PHY 输入时钟&#xff0c;频率 125MHz eth_rx_ctl Inpu…

arduino - NUCLEO-H723ZG - test

文章目录 arduino - NUCLEO-H723ZG - test概述笔记物理串口软串口备注END arduino - NUCLEO-H723ZG - test 概述 准备向NUCLEO-H723ZG上移植西门子飞达控制的Arduino程序. 先确认一下知识点和效果. 笔记 物理串口 NUCLEO-H723ZG在STM32 Arduino 库中, 只提供了一个串口 Se…