共享智能指针shared_ptr

共享智能指针

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

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

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

1.shared_ptr的初始化

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针shared_ptr 是一个模板类,如果要进行初始化有三种方式:通过构造函数、std::make_shared辅助函数以及reset方法。共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数use_count,函数原型如下:

// 管理当前对象的 shared_ptr 实例数量,或若无被管理对象则为 0。
long use_count() const noexcept;
1.1 通过构造函数初始化
// shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
std::shared_ptr<T> 智能指针名字(创建堆内存);

测试代码如下:

#include <iostream>
#include <memory>
using namespace std;int main()
{// 使用智能指针管理一块 int 型的堆内存shared_ptr<int> ptr1(new int(520));cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;// 使用智能指针管理一块字符数组对应的堆内存shared_ptr<char> ptr2(new char[12]);cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;// 创建智能指针对象, 不管理任何内存shared_ptr<int> ptr3;cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;// 创建智能指针对象, 初始化为空shared_ptr<int> ptr4(nullptr);cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;return 0;
}

测试代码输出的结果如下:

ptr1管理的内存引用计数: 1
ptr2管理的内存引用计数: 1
ptr3管理的内存引用计数: 0
ptr4管理的内存引用计数: 0

如果智能指针被初始化了一块有效内存,那么这块内存的引用计数+1,如果智能指针没有被初始化或者被初始化为nullptr空指针,引用计数不会+1。另外,不要使用一个原始指针初始化多个shared_ptr。

int *p = new int;
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);		// error, 编译不会报错, 运行会出错
1.2 通过拷贝和移动构造函数初始化

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

void test02() {// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1shared_ptr<int> ptr1(new int(520));cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;//调用拷贝构造函数shared_ptr<int> ptr2(ptr1);cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;shared_ptr<int> ptr3 = ptr1;cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;//调用移动构造函数shared_ptr<int> ptr4(std::move(ptr1));cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;std::shared_ptr<int> ptr5 = std::move(ptr2);cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
}

测试程序输入的结果:

ptr1管理的内存引用计数: 1
ptr2管理的内存引用计数: 2
ptr3管理的内存引用计数: 3
ptr4管理的内存引用计数: 3
ptr5管理的内存引用计数: 3

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

1.3 通过std::make_shared初始化

通过C++提供的std::make_shared() 就可以完成内存对象的创建并将其初始化给智能指针,函数源码如下:

template<class T, class... Args>
shared_ptr<T> make_shared(Args&&... args) {return std::allocate_shared<T>(std::allocator<T>(), std::forward<Args>(args)...);
}

这个函数模板使用了可变模板参数(variadic templates)和完美转发(perfect forwarding)的特性。它接受一个类型 T 和任意数量的参数(Args),然后调用 std::allocate_shared 来完成共享指针的初始化和对象的构造。

可变模板参数是指函数模板的参数数量是可变的。在std::make_shared的情况下,它允许我们以任意数量的参数初始化对象。

举例来说,假设有一个类 MyClass:

class MyClass {
public:MyClass(int a, double b, const std::string& c) {// 构造函数的实现}
};

可以使用 std::make_shared 来创建一个 shared_ptr 并调用 MyClass 的构造函数:

#include <memory>
#include <string>int main() {auto mySharedPtr = std::make_shared<MyClass>(42, 3.14, "Hello");// 使用 mySharedPtrreturn 0;
}

在这个例子中,MyClass 有一个带有三个参数的构造函数,而 std::make_shared 允许你提供这三个参数,以便在分配内存时调用构造函数进行对象初始化。可变模板参数的使用使得 std::make_shared 能够处理任意数量的构造函数参数。

测试代码如下:

#include <iostream>
#include <string>
#include <memory>
using namespace std;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 型的堆内存, 内部引用计数为 1shared_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;
}

测试代码输出:

ptr1管理的内存引用计数: 1
construct Test...
ptr2管理的内存引用计数: 1
construct Test, x = 520
ptr3管理的内存引用计数: 1
construct Test, str = 我是要成为海贼王的男人!!!
ptr4管理的内存引用计数: 1
destruct Test ...
destruct Test ...
destruct Test ...

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

1.4 通过reset方法初始化

共享智能指针类提供的std::shared_ptr::reset方法函数原型如下:

void reset() noexcept;template< class Y >
void reset( Y* ptr );template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );
  • ptr:指向要取得所有权的对象的指针
  • d:自定义的删除器(deleter)
  • aloc:自定义的分配器

其中最常见的是不带任何参数的形式和带一个指针参数的形式。

不带参数的reset

template<class Y>
void reset();

这个版本的 resetshared_ptr 置为空,即它不再拥有任何对象。如果此时 shared_ptr 是最后一个拥有某个对象的智能指针,它会调用对象的析构函数来释放资源。

std::shared_ptr<int> ptr = std::make_shared<int>(42);
ptr.reset();  // ptr 现在为空,且释放了之前的对象

带指针参数的reset

template<class Y>
void reset(Y* ptr);

这个版本的 reset 会替换 shared_ptr 当前所管理的对象,并且使用参数 ptr 所指向的对象。如果此时 shared_ptr 是最后一个拥有某个对象的智能指针,它会调用原对象的析构函数来释放资源,然后开始管理新的对象。

std::shared_ptr<int> ptr = std::make_shared<int>(42);
ptr.reset(new int(10));  // ptr 不再指向之前的对象,而是指向一个新的 int 对象

带指针和自定义删除器的 reset

template<class Y, class Deleter>
void reset(Y* ptr, Deleter deleter);

这个版本的 reset 允许你指定一个自定义的删除器(deleter),该删除器将在 shared_ptr 管理的对象销毁时被调用。这允许你使用 shared_ptr 管理通过非默认方式分配的资源。

std::shared_ptr<int> ptr(new int, [](int* p) { delete p; });
ptr.reset(new int, [](int* p) { delete[] p; });

带指针、自定义删除器和分配器的 reset

template<class Y, class Deleter, class Alloc>
void reset(Y* ptr, Deleter deleter, Alloc alloc);

这个版本的 reset 允许你指定自定义的删除器和分配器。通常情况下,std::shared_ptr 使用默认分配器 std::allocator 和默认删除器 delete

std::shared_ptr<int> ptr(new int, [](int* p) { delete p; }, std::allocator<int>());

来看一下这个例子:

#include <iostream>
#include <string>
#include <memory>
using namespace std;int main()
{// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1shared_ptr<int> ptr1 = make_shared<int>(520);shared_ptr<int> ptr2 = ptr1;shared_ptr<int> ptr3 = ptr1;shared_ptr<int> ptr4 = ptr1;cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; // 4cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; // 4cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; // 4cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; // 4ptr4.reset();cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; // 0ptr4.reset(new int(250));cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl; // 3cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl; // 3cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl; // 3cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl; // 1return 0;
}

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

1.5 获取原始指针

通过智能指针可以管理一个普通变量或者对象的地址,此时原始地址就不可见了。当我们想要修改变量或者对象中的值的时候,就需要从智能指针对象中先取出数据的原始内存的地址再操作,解决方案是调用共享智能指针类提供的get()方法,其函数原型如下:

T* get() const noexcept;

测试代码如下:

#include <iostream>
#include <string>
#include <memory>
using namespace std;int main()
{int len = 128;shared_ptr<char> ptr(new char[len]);// 得到指针的原始地址char* add = ptr.get();fill(ptr.get(), ptr.get() + len, '\0');const char* source = "我爱cpp";copy(source, source + strlen(source) + 1, ptr.get());cout << "string: " << ptr.get() << endl;shared_ptr<int> p(new int);*p = 100;cout << *p.get() << "  " << *p << endl; // 100 100return 0;
}

2.指定删除器

当智能指针管理的内存对应的引用计数变为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;
}

删除器函数也可以是lambda表达式,因此代码也可以写成下面这样:

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

在上面的代码中,lambda表达式的参数就是智能指针管理的内存的地址,有了这个地址之后函数体内部就可以完成删除操作了。

管理动态数组

在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指定为什么类型即可。具体处理代码如下:

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

另外,我们还可以自己封装一个make_shared_array方法来让shared_ptr支持数组,代码如下:

#include <iostream>
#include <memory>
using namespace std;template <typename T>
shared_ptr<T> make_share_array(size_t size)
{// 返回匿名对象return shared_ptr<T>(new T[size], default_delete<T[]>());
}int main()
{shared_ptr<int> ptr1 = make_share_array<int>(10);cout << ptr1.use_count() << endl;shared_ptr<char> ptr2 = make_share_array<char>(128);cout << ptr2.use_count() << endl;return 0;
}
资源的所有权转移

另一方面: 指定删除器也用于资源的所有权转移

删除器允许你将资源的所有权转移给智能指针。这对于使用自定义释放逻辑的资源管理类很有帮助,因为你可以将资源的释放责任委托给智能指针。

class CustomResource {
public:CustomResource(int* data) : data_(data) {}void release() { delete[] data_; }private:int* data_;
};// 将自定义资源类的释放函数作为删除器
std::shared_ptr<CustomResource> customPtr(new CustomResource(new int[10]), [](CustomResource* resource) {resource->release();delete resource;
});

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

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

相关文章

[ 云计算 | AWS 实践 ] 使用 Java 检查指定的密钥是否存在于给定的 Amazon S3 存储桶中

本文收录于【#云计算入门与实践 - AWS】专栏中&#xff0c;收录 AWS 入门与实践相关博文。 本文同步于个人公众号&#xff1a;【云计算洞察】 更多关于云计算技术内容敬请关注&#xff1a;CSDN【#云计算入门与实践 - AWS】专栏。 本系列已更新博文&#xff1a; [ 云计算 | …

C++ 智能指针和内存管理:使用指南和技巧

C是一门强大的编程语言&#xff0c;但是在内存管理方面却存在着一些问题。手动管理内存不仅费时费力&#xff0c;而且容易出错。因此&#xff0c;C中引入了智能指针这一概念&#xff0c;以更好地管理内存。 什么是智能指针&#xff1f; 在C中&#xff0c;内存的分配和释放都是…

AttributeError: ‘FieldInfo‘ object has no attribute ‘required‘.

诸神缄默不语-个人CSDN博文目录 这个bug是在安装doccano包之后&#xff0c;在运行transformers代码时出现的。 核心报错信息&#xff1a; RuntimeError: Failed to import transformers.models.bert.modeling_bert because of the following error (look up to see its trac…

Java 数组另类用法(字符来当数组下标使用)

一、原因 看力扣的时候发现有位大佬使用字符来当数组下标使用。 class Solution {public int lengthOfLongestSubstring(String s) {int result 0;int[] hash new int[130];int i 0;for(int j 0; j < s.length(); j) {while(hash[s.charAt(j)] > 0) {hash[s.charAt…

服务器入侵如何防护,业务被攻击如何处理,服务器安全防护方案

服务器是算是家用电脑的一种使用方法,主机不在用户家中,需要远程使用,在目前互联网时代占用很重要的位置&#xff0c;当然生活中也是应用广泛。服务器比普通计算机运行更快、负载更高、价格更贵。很多娱乐,工作都需要依靠服务器来运行整个体系&#xff0c;因此服务器的安全防护…

虚拟化逻辑架构: VM VirtualBox 指定6.0.24版本开启硬件辅助虚拟化功能

目录 一、实验 1.安装VM VirtualBox-6.0.24 2.安装VM VirtualBox-6.1.26 3.再次重新安装VM VirtualBox-6.0.24 二、问题 1.系统开机报错 2.Ubuntu系统无法自适应VM VirtualBox系统边框 3.VirtualBox如何开启无缝模式 3.Ubuntu如何查询软件是否已经安装 一、实验 1.安…

SCAU:主对角线上的元素之和

主对角线上的元素之和 Time Limit:1000MS Memory Limit:65535K 题型: 编程题 语言: G;GCC 描述 输入一个3行4列的整数矩阵&#xff0c;计算并输出主对角线上的元素之和输入格式 3行4列整数矩阵输出格式 主对角线上的元素之和输入样例 1 2 3 4 5 6 7 8 9 10 11 12输出…

react二次封装Modal和Drawer组件

目录 react二次封装Modal和Drawer组件01&#xff1a;Modal组件01-1 BaseModal.jsx01-2 使用BaseModal组件01-3 效果 02&#xff1a;Drawer组件02-1 BaseDrawer.jsx组件02-2 使用BaseDrawer组件02-3效果 react二次封装Modal和Drawer组件 npm i styled-components 01&#xff1…

详细学习PyQt5中的多线程

Pyqt5相关文章: 快速掌握Pyqt5的三种主窗口 快速掌握Pyqt5的2种弹簧 快速掌握Pyqt5的5种布局 快速弄懂Pyqt5的5种项目视图&#xff08;Item View&#xff09; 快速弄懂Pyqt5的4种项目部件&#xff08;Item Widget&#xff09; 快速掌握Pyqt5的6种按钮 快速掌握Pyqt5的10种容器&…

【Shell3】日常巡检1

#!/usr/bin/env bash# --------------------------------------------------------------------------------- # 控制台颜色 BLACK"\033[1;30m" RED"\033[1;31m" GREEN"\033[1;32m" YELLOW"\033[1;33m" BLUE"\033[1;34m" PU…

go语言学习-包管理

1、概念 1.1 什么是包 ***Go语言的包(package) ***是一种源码封装的方式,可以被看做是组相关的,并且通用的代码集合。这些包都有自己的独立的功能,然后在编写代码时,如果需要用到这些功能,可以导入包直接使用。 打印一些内容:fmt处理一些时间相关的: time处理一些数学相关…

移除元素、合并两个有序数组(leetcode)

一、移除元素 思路三&#xff1a; while(src<numsSize) 使用一个 while 循环来遍历数组。循环的条件是 src 必须小于 numsSize&#xff0c;以确保不会越界。 if(nums[src]!val) 如果当前 src 指向的元素不等于给定的值 val&#xff0c;则执行以下操作&#xff1a; nums[ds…

Leetcode 第 374 场双周赛 Problem D 100146. 统计感冒序列的数目(组合数学+阶乘+逆元)

Leetcode 第 374 场双周赛 Problem D 100146. 统计感冒序列的数目&#xff08;组合数学阶乘逆元&#xff09;题目 给你一个整数 n 和一个下标从 0 开始的整数数组 sick &#xff0c;数组按 升序 排序。有 n 位小朋友站成一排&#xff0c;按顺序编号为 0 到 n - 1 。数组 sick 包…

[RoFormer]论文实现:ROFORMER: ENHANCED TRANSFORMER WITH ROTARY POSITION EMBEDDING

文章目录 一、完整代码二、论文解读2.1 注意力机制2.2 绝对位置编码2.3 相对位置编码2.4 旋转位置编码Long-term decayAdaption for linear attention 2.5 模型效果 三、过程实现四、整体总结 论文&#xff1a;ROFORMER: ENHANCED TRANSFORMER WITH ROTARY POSITION EMBEDDING …

Java 使用itextpdf创建Pdf文件

DOM文件添加Maven依赖 <dependency><groupId>com.itextpdf</groupId><artifactId>itext7-core</artifactId><version>7.2.0</version><type>pom</type></dependency> 主要代码&#xff1a; PdfFont font PdfFo…

【数据结构】拆分详解 - 二叉树的链式存储结构

文章目录 一、前置说明二、二叉树的遍历  1. 前序、中序以及后序遍历   1.1 前序遍历   1.2 中序遍历   1.3 后序遍历 2. 层序遍历 三、常见接口实现  0. 递归中的分治思想  1. 查找与节点个数   1.1 节点个数   1.2 叶子节点个数   1.3 第k层节…

yo!这里是智能指针相关介绍

目录 前言 内存泄漏 RAII 智能指针原理 智能指针分类 auto_ptr unique_ptr shared_ptr 两个问题 线程安全 循环引用 后记 前言 对于智能指针&#xff0c;听起来很高大上&#xff0c;其实本质上就是一个类。为什么叫指针呢&#xff1f;因为可以像指针一样管理一块资…

linux 应用开发笔记---【I/O文件/基础篇 】

文章笔记来自于【速学Linux】手把手教你学嵌入式Linux C应用编程_哔哩哔哩_bilibili 一&#xff0c;什么是linux应用程序 1.运行在linux操作系统用户空间的程序 2.内核程序运行在内核空间&#xff0c;应用程序运行在用户空间 在终端执行的命令ls,ps。。。。。。都是运行在用…

使用gdb调试QEMU模拟的RISC-V平台程序

我们跑一个裸核程序&#xff0c;也就是不带操作系统的程序&#xff0c;然后使用gdb调试该程序。 首先编译目标程序&#xff0c;然后使用QEMU的kernel参数进行加载 qemu-system-riscv64 -s -S -bios opensbi.elf -m 4G -smp 4 -kernel my_program.x -nographic -s 让QEMU在12…

【MySQL的DQL查询语句】

MySQL的DQL查询语句-----在Navicat下 将学生表导入Navicat中查询语句查询一整张表查询年龄大于22年龄大于22的女生查找文科的学生查找六班的学生计算学生的总分 &#xff08;group by&#xff09;合并两表 &#xff08;join on xxxx&#xff09;合并两张表 并求总分先合并在聚合…