C++:C++Primer Plus(第六版):Chapter16 : string类和标准模板库:智能指针

news2024/11/29 4:54:00

Chapter16.2 : 智能指针模板类

  • 1.指针指针现实需求
    • 1.1 普通指针弊端
  • 1.2 智能指针现实需求
  • 2. 智能指针使用范式
    • 2.1 下面演绎三种智能指针用法
    • 注意一个问题
  • 3. 三种智能指针特点
    • 3.1 为何摒弃 auto_ptr
  • 3.2 选用 unique_ptr
  • 3.3 选用 shared_ptr
  • 4. 应该使用哪种智能指针

本章节我们主要讲解 智能模板类,并主要从下面几个方面入手:

  1. 智能指针出现背景和现实需求
  2. 智能指针 使用范式
  3. 智能指针种类以及该如何选型

1.指针指针现实需求

智能指针是行为类似于指针的类对象,但这种对象还有其他功能,它可以管理给指针分配的对象

1.1 普通指针弊端

先看下面函数

#include<string>
void remodel(std::string& str)
{
	std::string* ps = new std::string(str);
	....
	std = ps;
	return;
}

上面的代码呢可以已经发现了这样一个现象:
该函数分配在堆中的随想,从来不收回,这会导致内存泄漏,你可能也知道解决之道: 在 return 前添加下面语句,释放内存即可

delete ps

但是这仍然会有问题,情况下面的变体。

#include<string>
void remodel(std::string & str)
{
	std::string *ps = new std::string(str);
	...
	if (weird_thing())
		throw exception();
	str = *ps;
	delete ps;
	return;
}

很显然,出现异常时,delete将不再执行,因此还是会导致内存泄漏。

1.2 智能指针现实需求

  1. 基于1.1章节,我们知道,你可能会在函数执行完毕,忘记 delete ps 或者在 delete ps 执行之前,代码发生异常。这两点都会无法释放内存,造成内存泄漏。
  2. 所以,我们需要有这样一种情况:函数栈在执行完毕后,指针ps 占的内存被释放,指针ps 指向的堆内存也会自动释放(这个可以通过析构函数释放)
  3. 但是 ps 只是一个常规指针,不是有析构函数的类对象,如果它是对象,那么可以在对象过期时,让它的析构函数删除指向的内存。
  4. 这就是 auto_ptr , unique_ptr和 shared_ptr 背后的思想所在。
  5. auto_ptr 是C++98 提供的解决方案,C++11 已经将摒弃,并提供了另外两种解决方案, 如果您的编译器不支持其他两种方案,那么 auto_ptr 是唯一的选择。

💚💚💚
下面来看一个使用智能指针的例子

在这里插入图片描述
很显然 auto_ptr 定义了类似指针的对象,可以将 new 获取(直接或间接)的地址赋给对象,当智能指针过期时,对象其析构函数 将使用 delete 来释放内存。

  1. 如果将 new返回的地址赋给这些对象,将无需记住稍后需要释放的内存。
  2. 智能指针在过期时,这些内存会自动释放。

2. 智能指针使用范式

  1. 要创建智能指针对象,必须包含头文件 memory, 下面是 auto_ptr 文件模板定义如下。
// 下面是伪代码
#include<memory>
#include<string>
template<class X>
class auto_ptr
{
public:
	eplicit auto_ptr(X* p = 0);
}

void create_auto_ptr()
{
	auto_ptr<double> pd(new double); // pd an auto_ptr to double
	auto_ptr<string> ps(new string);  // ps an auto_ptr string 
}
  1. new double/ new string 是new 返回的指针,指向新分配的内存块,它是构造函数 auto_ptr 的参数,对应原型中形参 p 的实参。

因此第一节的例子就可以这样转换
💚 包含头文件 memory
💚 指向 string 的指针 替换为 指向 string 的智能指针对象
💚 删除 delete语句

#include<string>
#include<memory>
void remodel(std::string & str)
{
	// std::string *ps = new std::string(str);
	std::auto_ptr<str::string> ps (new std::string(str));
	...
	if (weird_thing())
		throw exception();
	str = *ps;
	// delete ps;   no longer needed
	return;
}

2.1 下面演绎三种智能指针用法

#incdlue<iostream>
#include<string>
#include<memory>
class Report
{
private:
    std::string str;
public:
	Report(const std::string s):str(s)
	{
		std::cout<< "Objcet create \n";
	}
	~Report()
	{
		std::cout << "Object deleted\n";
    }
	void comment() const
	{
		std::cout <<str << std::endl;
    }
}

int main()
{
    {
    	// 将 new Report 对象地址 赋值给 auto_ptr 构造函数 实参p
		std::auto_ptr<Report> ps(new Report("using auto_ptr"));
		ps->comment();  // use ->invoke a member function
    }
	
	{
    	// 将 new Report 对象地址 赋值给 shared_ptr 构造函数 实参p
		std::shared_ptr<Report> ps(new Report("using shared_ptr"));
		ps->comment();  // use ->invoke a member function
    }
	{
    	// 将 new Report 对象地址 赋值给 unique_ptr 构造函数 实参p
		std::unique_ptr<Report> ps(new Report("using unique_ptr"));
		ps->comment();  // use ->invoke a member function
    }
	return 0;
}

// 打印结果
Objcet create 
using auto_ptr
Object deleted

Objcet create 
using shared_ptr
Object deleted

Objcet create 
using unique_ptr
Object deleted
  1. 所有智能指针类都有一个 explicit 构造函数,该构造函数将指针作为参数,因此不需要自动将指针转换为 智能指针对象,更不要将指针转换成智能指针。如
shared_ptr<double> pd;
double *p_reg = new double;
pd = p_reg;  // not allow (implicit conversion)
pd = shared_ptr<double>(p_reg);  // allow (explicit conversion)

shared_ptr<double> pshared = p_reg;  // not allowed (implicit conversion)
shared_ptr<double> pshared(p_reg);  // allowed (explicit conversion)

注意一个问题

下面这个问题,全部三种智能指针都应该避免

string vacation("I wandeed lonely as a child");
shared_ptr<string> pvac(&vacation);  // error 

// pvac 过期时,程序将把 delete 运算符用于非 堆内存,这是 错误的

3. 三种智能指针特点

3.1 为何摒弃 auto_ptr

先看下面语句

string str = new string("I wandeed lonely as a child");
string *ps = &str;
string* vocation;
vocation = ps;  // 将指针赋值给另一个指针
delete ps;
delete vocation;

💚 上面这段语句会完成什么操作了 ?

  1. 这两个指针都指向一个string 对象,这是不能接受的,因为程序将视图删除同一个对象 两次,一次是 ps 过期时,另一次是 vocation过期时,要避免这种问题方法有很多 。
    🧡
  2. 定义赋值运算符:使之执行深度赋值,这样两个指针指向不同的对象,其中的一个对象是另一个对象的副本。
  3. 建立所有权(ownership) 概念:对于特定对象,只能有一个指针拥有它,这样只有拥有对象的指针指针的析构函数才可以删除,然后,让赋值操作转让所有权,这就是 auto_ptr 和unique_ptr 的策略,但是 unique_ptr 的策略更严格
  4. 创建智能更高的指针,跟踪引用特定对象的智能指针,这被称为引用计数 (reference counting)。

🚀 下面我们来通过另一个例子详细说明:为何摒弃 auto_ptr。

#include<iostream>
#include<string>
#include<memory>

int main()
{

    using namespace std;
	auto_ptr<string> film[5]=
	{
		
		auto_ptr<string> (new string("Flowl Bals")),
		auto_ptr<string> (new string("Duck walks")),
		auto_ptr<string> (new string("Chicken run")),
		auto_ptr<string> (new string("Turkey errors")),
		auto_ptr<string> (new string("Goose Eggs"))
	};
	
	auto_ptr<string> pwin;
	pwin = film[2]; // film[2] loses ownership(将对象的所有权转让给 pwin)
	for(int i = 0;i<5;i++)
	{
		
		cout << *film[i] << endl;
	}
	cout<< "the winner is: " << *pwin << "\n";
	
	return 0;
}

// 打印结果
Flowl Bals
Duck walks
Segmentation fault

很显然,这是由于错误的使用 auto_ptr 可能导致的问题(但是这种行为是不确定的,其行为可能随系统而定),这里的问题是由于下面的语句将 :所有权 从 films[2] 转让给 pwin

pwin = films[2] // films[2] loses ownership
这导致 films[2] 不再引用 该字符串,在 auto_ptr 放弃对象所有权后,你又通过 *films[2] 访问指向的对象,却发现这是一个空指针。
这便是 auto_ptr 令人讨厌的地方。

3.2 选用 unique_ptr

如果我们将 auto_ptr 修改成 unipue_ptr 了 ?

#include<iostream>
#include<string>
#include<memory>

int main()
{

    using namespace std;
	unique_ptr<string> film[5]=
	{
		
		unique_ptr<string> (new string("Flowl Bals")),
		unique_ptr<string> (new string("Duck walks")),
		unique_ptr<string> (new string("Chicken run")),
		unique_ptr<string> (new string("Turkey errors")),
		unique_ptr<string> (new string("Goose Eggs"))
	};
	
	unique_ptr<string> pwin;
	pwin = film[2]; // film[2] loses ownership(将对象的所有权转让给 pwin)
	for(int i = 0;i<5;i++)
	{
		
		cout << *film[i] << endl;
	}
	cout<< "the winner is: " << *pwin << "\n";
	
	return 0;
}

好家伙,直接报错,都不能编译通过,

💚💚💚这便是 unique_str 与 auto_ptr 的区别

  1. 它避免了 films[2] 指针不再指向有效数据的问题,即悬挂指针问题。
    在这里插入图片描述

3.3 选用 shared_ptr

#include<iostream>
#include<string>
#include<memory>

int main()
{

    using namespace std;
	shared_ptr<string> film[5]=
	{
		
		shared_ptr<string> (new string("Flowl Bals")),
		shared_ptr<string> (new string("Duck walks")),
		shared_ptr<string> (new string("Chicken run")),
		shared_ptr<string> (new string("Turkey errors")),
		shared_ptr<string> (new string("Goose Eggs"))
	};
	
	shared_ptr<string> pwin;
	pwin = film[2]; // film[2] loses ownership(将对象的所有权转让给 pwin)
	for(int i = 0;i<5;i++)
	{
		
		cout << *film[i] << endl;
	}
	cout<< "the winner is: " << *pwin << "\n";
	
	return 0;
}
// 打印结果
Flowl Bals
Duck walks
Chicken run
Turkey errors
Goose Eggs
the winner is: Chicken run

这次 pwin和films[2] 指向同一个对象。

  1. 引用计数从 1 增加到 2
  2. 在程序结尾,后声明的 pwin先调用析构函数,该析构函数将引用计数降低到1
  3. 对 films[2] 调用析构函数时,引用计数降低到0 ,这个时候就可以释放以前分配的空间。

4. 应该使用哪种智能指针

  1. 如果程序使用多个指向同一个对象的指针,应选择 shared_ptr
  2. STL容器包含指针,很多STL算法都支持 复制和赋值操作,这些操作可用于 shared_ptr
  3. 如果您的编译器没有体用 shared_ptr 可以使用 Boost库提供的 shared_ptr
  4. 如果程序不需要多个指向同一个对象的指针,则可以使用 unique_ptr 。

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

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

相关文章

学好数据结构的秘诀

学好数据结构的秘诀 作为计算机专业的一名“老兵”&#xff0c;笔者从事数据结构和算法的研究已经近20余年了&#xff0c;在学习的过程中&#xff0c;也会遇到一些问题&#xff0c;但在解决问题时&#xff0c;积累了一些经验&#xff0c;为了让读者在学习数据结构的过程中少走…

Vue2全家桶 (Vue2、VueRouter、Vuex) 笔记

前言 有错的地方希望大家能够帮忙指正一下&#xff1b;本文只是对Vue2全家桶的基本语法进行记录。给孩子点点关注吧&#xff01;&#x1f62d; 壹、Vue2 Vue&#xff1a;一个 JavaScript 渐进式框架&#xff1b; 渐进式&#xff1a;逐渐使用&#xff0c;集合更多的功能&am…

一文总结 Google I/O 2023

今日凌晨举行的谷歌 I / O 2023 开发者大会上&#xff0c;如果说和往年有什么不同&#xff0c;那么应该就是更大力度的 All in AI 。 毕竟在去年 I/O 宣布重点投入 AI 之后&#xff0c;年末却被 OpenAI 的 ChatGPT “越塔偷家”&#xff0c;匆忙攒出一个发布会还让 Bard 爆出事…

国内最大规模上云实践 | 鹅厂如何在云原生2.0时代“挖呀挖”?

&#x1f449;腾小云导读 2022 年 10 月&#xff0c;腾讯自研业务产品全面完成云原生上云。自研业务产品云上规模已突破 5000w CPU&#xff0c;借助云原生的技术优势&#xff0c;全面提升了腾讯自研业务产品的运营效率&#xff0c;在此过程中我们也对腾讯云产品进行了打磨和验证…

适合Python 初学者入门教程和资源

如果您是 Python 的初学者&#xff0c;以下是一些入门教程和资源可以帮助您快速开始&#xff1a; 1、Python 官方教程: Python 官方网站提供了完整的入门教程&#xff0c;包括语言基础、面向对象编程、模块、文件处理等方面的内容。这是一个非常全面和权威的资源。 2、Codecade…

盖雅工场重磅发布「劳动力账户」,助力企业实现全面工时成本管理

2023年5月10日&#xff0c;亚太地区领先的劳动力管理云服务商盖雅工场宣布&#xff0c;在原有的智能排班和实时考勤系统基础之上&#xff0c;正式推出全新模块「劳动力账户」&#xff0c;通过为企业里的每一位员工建立劳动力账户&#xff0c;实现工时与成本的全面管控&#xff…

一文打通原生Shiro使用

目录 环境准备 添加依赖 INI 文件 登录认证 登录认证概念 登录认证基本流程 登录认证实例 身份认证流程 角色、授权 授权概念 授权方式 授权流程 授权实例 Shiro 加密 Shiro 自定义登录认证 环境准备 Shiro不依赖容器&#xff0c;直接创建maven工程即可 添加依…

机器学习——线性回归

机器学习——线性回归 文章目录 机器学习——线性回归[toc]1 模型设定2 训练模型3 模型预测4 交叉验证 基于Python实现线性回归、预测和建模评估。 1 模型设定 以Boston数据集为例&#xff0c;其中MEDV是标签&#xff0c;其余均为特征变量 CRIM per capita crime rate by tow…

玩转ChatGPT:Excel操作初探

一、写在前面 首先还是让小Chat推销下自己&#xff1a; Excel 表格制作是个技术活&#xff0c;你掌握了吗&#xff1f;没关系&#xff0c;现在有了 ChatGPT&#xff0c;让 Excel 辅助操作变得更简单&#xff01;再也不用苦恼于数据分析和整理了&#xff0c;让 ChatGPT 成为你…

C语言:简单的扫雷游戏

扫雷游戏想必大家都玩过&#xff0c;像一些大佬呀&#xff0c;十几秒能通关&#xff0c;给大佬点赞&#xff0c;那咱们也设计一个扫雷游戏&#xff0c;具体怎么实现呢&#xff0c;跟上我的脚步&#xff0c;看看用C语言怎么实现简单的扫雷游戏。 一&#xff1a;扫雷游戏的框架 二…

【Linux】冯诺依曼体系结构以及操作系统的初步认知

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;Linux的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录 前言一、冯诺依曼体系结构二、操…

HTB靶机010-SolidState-WP

#oscp SolidState.md 靶机IP 10.10.10.51 scan Nmap : ┌──(xavier㉿kali)-[~] └─$ sudo nmap -sSV -T4 -F 10.10.10.51 Starting Nmap 7.93 ( https://nmap.org ) at 2023-04-10 13:14 CST Nmap scan report for 10.10.10.51 Host is up (0.77s latency). Not shown: …

如果你还不知道SAGA,那这篇不容错过!|分布式事务系列(五)

这是分布式事务系列的第五篇&#xff0c;如果之前文章没读请自行前往。精华专题&#xff0c;强烈建议收藏。 ‍本文详细讲解了分布式事务解决方案——SAGA。 点击上方“后端开发技术”&#xff0c;选择“设为星标” &#xff0c;优质资源及时送达 SAGA事务 什么是SAGA事务 SAGA…

使用maven引入第三方jar包以及打包

我们知道&#xff0c;Maven 是通过仓库对依赖进行管理的&#xff0c;当 Maven 项目需要某个依赖时&#xff0c;只要其 POM 中声明了依赖的坐标信息&#xff0c;Maven 就会自动从仓库中去下载该构件使用。但在实际的开发过程中&#xff0c;经常会遇到一种情况&#xff1a;对接第…

《基于智能手机PPG信号处理和机器学习的非侵入式血糖监测系统》阅读笔记

目录 一、论文摘要 二、论文十问 Q1&#xff1a;论文试图解决什么问题&#xff1f; Q2&#xff1a;这是否是一个新的问题&#xff1f; Q3&#xff1a;这篇文章要验证一个什么科学假设&#xff1f; Q4&#xff1a;有哪些相关研究&#xff1f;如何归类&#xff1f;谁是这一课…

单片机GD32F303RCT6 (Macos环境)开发 (十五)—— i2c1采用DMA方式的读写函数

i2c1采用DMA方式的读写函数 1、关于i2c1的DMA的映射如图 2、关于代码的宏定义配置 Application目录的Makefile中 ENABLE_I2C_TEST yes才会编译I2C1的相关代码。 同时修改i2c.h文件&#xff0c;定义I2C1_MODE为I2C1_MODE_DMA&#xff0c;这样i2c1的配置为dma模式。 #define …

Microelectronic学习章节总结(3)-- gate level to physical level design

文章目录 数字逻辑设计的主要流程logic familyCMOSTransistor实现的时序逻辑 PSUDO-NMOSDCVSL(Differential Cascode Voltage Swing Logic)DOMINOC2MOS&#xff08;Clocked CMOS&#xff09;TSPC&#xff08;True Single Phase Clocking&#xff09; Transistor behaviorCMOS i…

【C生万物】 指针篇 (进级) 00

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; &#x1f449; 专栏&#xff1a;《C生万物 | 先来学C》&#x1f448; 前言&#xff1a; 前面已经带大家学习了初级的指针&#xff0c;先回顾一下重点&#xff1a;指针是用来存…

数据库sql语句(视图的创建)

例题&#xff1a; 建表&#xff1a;要注意各表之间的索引联系&#xff0c;建表先后顺序很重要&#xff0c;不然建不了&#xff0c;例如先建dept&#xff0c;在建其他表&#xff0c;先在dept插入数据&#xff0c;再在其他表插入数据 &#xff08;1&#xff09; create table …

SW simulation解算器选取

接下来重点介绍以下4种解算器&#xff0c;并对解算器进行实际的计算测试。1)Direct sparse解算器&#xff1a;稀疏矩阵解算器&#xff0c;目前使用最为广泛也是发展最为成熟的解算器&#xff0c;几乎所有的有限元分析软件都有该解算器。它可以用于大多数计算模型&#xff0c;堪…