C++智能指针(一)——shared_ptr初探

news2024/11/18 8:16:29

文章目录

  • 1. 普通指针存在的问题
  • 2. Class shared_ptr
    • 2.1 使用 shared_ptr
      • 2.1.1 初始化 shared_ptr
      • 2.1.2 reset
      • 2.1.3 访问数据
      • 2.1.4 use_count()
  • 3. Deleter
    • 3.1 定义一个 Deleter
    • 3.2 处理数组


1. 普通指针存在的问题

智能指针的引入,是为了解决普通指针在使用过程中存在的一些问题:其中内存泄漏以及空悬指针是最主要的问题。

正常使用普通指针,我们需要 new 分配内存,使用 delete 释放资源,一旦项目很庞大,尤其是在多个地方共享同一个指针时,产生内存泄漏的风险很大,且需要更多的代码来管理指针。

下面举一个具体实例,比如两个对象共享同一个指针,此时对于该指针在什么时候释放需要更多地代码来判断,以防止内存泄漏与访问空悬指针。

#include <iostream>
#include <string>
class Person
{
public:
	string name;
	Person* child;
	
	Person(const string& n, Person* c = nullptr) : name(n), child(c) {
	}
    
    ~Person() {
		std::cout << "delete" << name << std::endl;
	}
}

int main()
{
	Person* son = new Person("hhhcbw");
	Person father("c", son);
	Person mother("z", son);
	delete son;
	std::cout << father.child->name << std::endl; // ERROR: ask hanging pointer
}

为了解决普通指针的痛点,引入智能指针。


2. Class shared_ptr

shared_ptr 从字面就可以看出,该智能指针类主要用于共享资源,其能保证当最后一个对对象的引用被删除后,对象本身被删除(包括一些内存与资源的释放)。

2.1 使用 shared_ptr

使用 shared_ptr 与使用普通指针差不多。可以赋值,拷贝以及比较 shared_ptr,也可以使用操作符 *-> 来访问指针指向的对象。举一个例子:

#include <iostream>
#include <string>
#include <vector>
#include <memory>
using namespace std;
int main()
{
	// two shared pointers representing two persons by their name
	shared_ptr<string> pNico(new string("nico"));
	shared_ptr<string> pJutta(new string("jutta"));
	// capitalize person names
	(*pNico)[0] = ’N’;
	pJutta->replace(0,1,"J");
	// put them multiple times in a container
	vector<shared_ptr<string>> whoMadeCoffee;
	whoMadeCoffee.push_back(pJutta);
	whoMadeCoffee.push_back(pJutta);
	whoMadeCoffee.push_back(pNico);
	whoMadeCoffee.push_back(pJutta);
	whoMadeCoffee.push_back(pNico);
	// print all elements
	for (auto ptr : whoMadeCoffee) {
	cout << *ptr << " ";
	}
	cout << endl;
	// overwrite a name again
	*pNico = "Nicolai";
	// print all elements again
	for (auto ptr : whoMadeCoffee) {
	cout << *ptr << " ";
	}
	cout << endl;
	// print some internal data
	cout << "use_count: " << whoMadeCoffee[0].use_count() << endl;
}

上面的代码,具体表现如下图所示
输出如下

Jutta Jutta Nico Jutta Nico
Jutta Jutta Nicolai Jutta Nicolai
use_count: 4

2.1.1 初始化 shared_ptr

shared_ptr 类定义在 <memory> 里,需要注意的是,shared_ptr 的使用一个指针作为单独参数的构造函数是显式的(explicit),因此不能使用赋值符号,来将普通指针赋值给 shared_ptr

shared_ptr<string> pNico = new string("nico"); // ERROR
shared_ptr<string> pNico{new string("nico")}; // OK

也可以使用函数 make_shared() 来创建 shared_ptr,且这样更快且更安全:因为相比于前面的初始化的两次分配内存(一次给对象,一次给共享指针的共享数据),使用函数 make_shared() 只需要一次分配内存,完成两个步骤。

注意,尽量不要对一个已有普通指针,创建共享指针,如:

string* pNico = new string("nico");
shared_ptr<string> spNico(pNico);

如果pNico被设为nullptr,spNico.use_count()=1 且字符串未被释放,正常输出 nico
但如果spNico被设为nullptr,此时字符串被释放,但pNico还保存该地址!!

2.1.2 reset

可以先声明一个共享指针,然后给该共享指针分配一个新的指针。当然,不能使用赋值操作,要使用 reset() 方法:

shared_ptr<string> pNico4;
pNico4 = new string("nico"); // ERROR: no assignment for ordinary pointers
pNico4.reset(new string("nico")); // OK

2.1.3 访问数据

与普通指针类似,使用 *->

(*pNico)[0] = ’N’;
pJutta->replace(0,1,"J");

2.1.4 use_count()

use_count() 表示当前拥有该对象的所有共享指针的数量,当一个共享指针被删除后,use_count()-1,反之,use_count()+1

上面例子中,pJutta 本身算一个,容器 vector 里还有三个,所有 use_count() = 4


3. Deleter

当最后一个拥有者被删除后,共享指针为对象调用 delete 进行内存和资源的释放。这不一定在作用域结束处发生,比如上面的例子中,当给 pNico 赋值 nullptr 且在将 vector resize 为 2, 也会导致最后一个拥有者被删除,以至调用 delete

3.1 定义一个 Deleter

我们甚至可以自定义 Deleter,例如在删除引用对象前输出一条信息:

shared_ptr<string> pNico(new string("nico"),
[](string* p) {
cout << "delete " << *p << endl;
delete p;
});
...
pNico = nullptr; // pNico does not refer to the string any longer
whoMadeCoffee.resize(2); // all copies of the string in pNico are destroyed

这里传入一个lambda表达式,作为 shared_ptr 构造函数的第二个参数,当然对于任何可调用的对象都是可以的,比如函数与重载了()运算符的类与std::function,比如下面的代码就是重载了 () 运算符的类:

#include <string>
#include <fstream> // for ofstream
#include <memory> // for shared_ptr
#include <cstdio> // for remove()
class FileDeleter
{
private:
std::string filename;
public:
FileDeleter (const std::string& fn)
: filename(fn) {
}
void operator () (std::ofstream* fp) {
fp->close(); // close.file
std::remove(filename.c_str()); // delete file
}
};

int main()
{
// create and open temporary file:
std::shared_ptr<std::ofstream> fp(new std::ofstream("tmpfile.txt"),
FileDeleter("tmpfile.txt"));
...
}

3.2 处理数组

shared_ptr 提供的默认deleter调用 delete 而不是 delete[]。这意味着默认 deleter只有在共享指针拥有的是一个单独由new创建的对象才有效。需要注意的是,给一个数组创建共享指针是可能的,但是是错误的:

std::shared_ptr<int> p(new int[10]); // ERROR, but compiles

所以,如果要使用 new[] 来创建一个对象数组,需要定义自己的 deleter。可以传入一个函数、function object或lambda,在内部调用 delete[],例如:

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

也可以使用提供给 unique_ptr 的官方helper,其内部调用 delele[]

std::shared_ptr<int> p(new int[10],
std::default_delete<int[]>());

当然,unique_ptrshared_ptr 在数组的处理上有一定的区别,更详细地会在 unique_ptr 讲解

std::unique_ptr<int[]> p(new int[10]); // OK
std::shared_ptr<int[]> p(new int[10]); // ERROR: does not compile
std::unique_ptr<int,void(*)(int*)> p(new int[10],
[](int* p) {
delete[] p;
});

shared_pr 不提供操作符 []

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

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

相关文章

Android 13.0 SystemUI修改状态栏电池图标样式为横屏显示

1.概述 在13.0的产品定制化开发中,对于原生系统中SystemUId 状态栏的电池图标是竖着显示的,一般手机的电池图标都是横屏显示的 可以觉得样式挺不错的,所以由于产品开发要求电池图标横着显示和手机的样式一样,所以就得重新更换SystemUI状态栏的电池样式了 如图: 2.SystemUI…

区块链金融的开发流程

区块链金融应用的开发流程与一般的软件开发流程有许多相似之处&#xff0c;但它还涉及到智能合约的编写、区块链网络集成和加密货币处理等特定方面的工作。以下是一般区块链金融应用的开发流程&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件…

如何查自己名下有多少个微信号?

99%的人都不知道微信有这个功能通过微信可以查自己名下绑定了多少个微信账号再也不担心身份证被别人用来绑定微信了姐妹们快去查一下吧&#xff01; ①打开微信&#xff0c;点击【我】→点击【设置】 ②点击【通用】→ 点击【辅助功能】→ 点击【微信支付】 ③点击【帮助中心】…

MDK自动生成带校验带SVN版本号的升级文件

MDK自动生成带校验带SVN版本号的升级文件 获取SVN版本信息 确保SVN安装了命令行工具&#xff0c;默认安装时不会安装命令行工具 编写一个模板头文件 svn_version.temp.h, 版本号格式为 1_0_0_SVN版本号 #ifndef __SVN_VERSION_H #define __SVN_VERSION_H#define SVN_REVISIO…

性能分析工具的使用(超详细)

数据库服务器的优化步骤 整个流程划分成了观察&#xff08;Show status&#xff09;和行动&#xff08;Action&#xff09;两个部分。字母 S 的部分代表观察&#xff08;会使用相应的分析工具&#xff09;&#xff0c;字母 A 代表的部分是行动&#xff08;对应分析可以采取的行…

Linux下设备树、pinctrl和gpio子系统、LED灯驱动实验

文章目录 设备树常用的of函数pinctrl子系统gpio子系统LED灯驱动实验修改设备树文件编写驱动代码执行结果在LED驱动代码中加入内核定时器 设备树 描述设备树的文件叫做DTS(Device Tree Source)&#xff0c;这个DTS文件采用树形结构描述板级设备&#xff0c;也就是开发板上的设备…

NEFU离散数学实验1-排列组合

相关概念 在离散数学中&#xff0c;组合数是一种用于计算从n个不同元素中选取m个元素的方式。以下是一些与组合数相关的概念&#xff1a; 排列&#xff1a;从n个不同元素中选取m个元素进行排列&#xff0c;排列数用P(n, m)表示&#xff0c;计算公式为P(n, m) n! / (n - m)! …

springboot篮球论坛系统springboot034

大家好✌&#xff01;我是CZ淡陌。一名专注以理论为基础实战为主的技术博主&#xff0c;将再这里为大家分享优质的实战项目&#xff0c;本人在Java毕业设计领域有多年的经验&#xff0c;陆续会更新更多优质的Java实战项目&#xff0c;希望你能有所收获&#xff0c;少走一些弯路…

渗透测试怎么入门?(超详细解读)

1. 什么是渗透测试 渗透测试就是模拟真实黑客的攻击手法对目标网站或主机进行全面的安全评估&#xff0c;与黑客攻击不一样的是&#xff0c;渗透测试的目的是尽可能多地发现安全漏洞&#xff0c;而真实黑客攻击只要发现一处入侵点即可以进入目标系统。 一名优秀的渗透测试工程…

ubuntu安装Miniconda并举例使用

更新系统包 sudo apt update sudo apt upgrade官网下载Miniconda&#xff0c;最好是实体机下载后放进虚拟机&#xff0c;方法可以参考Xftp 7连接服务器或者本地虚拟机文章 https://docs.conda.io/en/latest/miniconda.html#linux-installers 进入安装目录执行&#xff0c;右键…

行业追踪,2023-10-11

自动复盘 2023-10-11 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

什么是大数据,大数据简介

大数据的概念通俗的说法 大数据&#xff0c;按照我的理解比较通俗易懂的是在数据量很多很大的情况下数据处理速度需要足够快&#xff0c;用我们以前传统意义上的的技术比如关系型数据库mysql没办法处理或者处理起来非常复杂&#xff0c;必须有一些新的处理技术也就是大数据处理…

网工内推 | 实施工程师,有软考证书优先,上市公司,最高14薪

01 新点软件 招聘岗位&#xff1a;实施工程师 职责描述&#xff1a; 1、负责一线项目组对接&#xff0c;完成项目前期信息、需求收集&#xff1b; 2、负责需求验证、管控、上线专项跟进工作&#xff1b; 3、负责在推进过程中总结与沉淀&#xff0c;提升优化对接规范/效率&…

windows 下编译libcurl openssl

参考 编译libcurl-openssl 1、拉取opensssl 建议指定为最新的发布版本 git clone -b openssl-3.1.3 --recurse-submodules https://github.com/openssl/openssl.git2、拉取curl 建议指定为最新的发布版本 git clone -b curl-8_3_0 --recurse-submodules https://github.c…

WIPO绿色专利分类范围清单

WIPO绿色专利分类范围清单 1、来源&#xff1a;WIPO绿色专利分类范围清单来源于网址&#xff1a; https://www.wipo.int/classifications/ipc/green-inventory/home&#xff09; 2、范围&#xff1a;全球范围 3、指标为key、class、IPC、l1Title、l2Title、l3Title、l4Titl…

C进阶-自定义类型:结构体、枚举、联合

本章重点&#xff1a; 结构体&#xff1a; 结构体类型的声明 结构的自引用 结构体变量的定义和初始化 结构体内存对齐 结构体传参 结构体实现位段&#xff08;位段的填充&可移植性&#xff09; 1 结构体的声明 1.1 结构的基础知识 结构是一些值的集合&#xff0c;这些值称…

H3C交换机如何配置SSH服务

环境&#xff1a; H3C S6520 version 7.1.070, Release 6530P02 问题描述&#xff1a; H3C交换机如何配置SSH服务 组网需求 配置Host&#xff08;SSH客户端&#xff09;与Switch建立本地连接。Host采用SSH协议登录到Switch上&#xff0c;以保证数据信息交换的安全。SSH用…

深度学习_1_基本语法

数据结构 代码&#xff1a; import torchx torch.arange(12)##产生长度为12的一维张量print(x)##X x.resize(3, 4)##被弃用##print(X)y torch.reshape(x, (3, 4))##修改向量为矩阵&#xff0c;一维变二维print(y)print(y.size())xx torch.zeros((2, 3, 4))##三维矩阵&…

Ubuntu编译安装colmap遇到的几个问题以及解决

总体安装过程已经很明白了&#xff0c;写的人很多了&#xff0c;我就不赘述了&#xff0c;可以参考这里或者其他博客。我主要记录几个我遇到的问题以及解决方法。 1、cmake报错&#xff1a;No CMAKE_CUDA_COMPILER could be found. 这个原因是没找到cuda和nvcc目录&#xff0…

项目经理必看!4个万能公式,轻松搞定即兴发言!

大家好&#xff0c;我是老原。 项目经理经常会遇到一些需求会、演讲等需要开口发言的场合&#xff0c;而且大多数都挺突然的。 你永远不知道自己会在什么情况下&#xff0c;就被cue起来说两句。 可能是临时汇报&#xff1b; 也可能是开会时&#xff0c;领导突然询问你的意见…