c++智能指针之auto_ptr详解(有源码有实例)

2023-11-16

前言

内存泄漏大概是每一个c/c++程序员最深恶痛绝的问题,因为大部分此类问题都是令广大c程序员很抓狂,掉头发的疑难杂症。而内存泄漏的根本原因就是指针的使用不当引起的,例如指针指向的内存没有释放,导致产生了程序无法控制的内存块,而随着程序不断执行,这样的内存越积越多,最终导致程序使用的内存空间不够导致宕机等一些严重的后果。为了解决这种让人讨厌的问题,c++提供了一系列的智能指针例如auto_ptr(c++98),unique_ptr,shared_ptr和weak_ptr,接下来我们主要来了解一下auto_ptr。

什么是auto_ptr

在了解auto_ptr之前,我们先来聊一聊智能指针的设计思想。我们都知道当我们创建了一个类的对象的时候,当对象过期时会自动调用类的析构函数来销毁该对象,而智能指针其实也是将基本类型指针封装为类对象指针,并在析构函数里编写delete语句删除指针指向的内存空间。当然为了满足不同基本数据类型的要求,肯定会把这样的类设计成一个类模板。所以,auto_ptr也是c++标准库提供的类模板,并且对指针相关操作进行重载,因此,这样的类对象就可以被当做普通指针来使用。
接下来就让我们一起来学习一下auto_ptr的使用吧。

auto_ptr的使用

1.要使用auto_ptr首先要包含以下头文件。

#include <memory>

2.如何初始化auto_ptr对象?
有如下几种方式初始化auto_ptr的对象:

  1. 直接构造法:
auto_ptr<int> ptr(new int(10)); //创建了一个auto_ptr对象,指向整型数字10 
  1. 用已存在的普通指针来构造:
int *p = new int(10);
auto_ptr<int> ptr(p);

3)用已存在的智能指针来构造:

auto_ptr<int> ptr(new int(10));//新建一个智能指针对象
auto_ptr<int> ptr2(ptr);//调用拷贝构造函数来构造

在这里我们应该清楚,智能指针是有所有权概念的,即一块内存是只供一个智能指针独享的,当把一个智能指针赋值给另一个智能指针时则发生了所有权转移,例如上面的方式3,调用了拷贝构造函数之后,ptr指针就悬空了。这里我们给出一个小程序测试一下:

#include <iostream>
#include <memory>//包含智能指针的头文件
using namespace std;

int main()
{
	auto_ptr<int> ptr(new int(10));
	cout << *ptr << endl;
	auto_ptr<int> ptr2(ptr);
	cout << *ptr2 << endl;
	if(ptr.get() == NULL)//注意
		cout << "prt is null ptr" << endl;
	return 0;
}

上面的测试程序需要注意的一点是:不像普通指针我们可以用下面的语句来判断指针是否为空:

if(ptr == NULL)//普通指针可以这样来判断指针是否为空

智能指针没有重载==的操作符,但是它提供了get()函数来获取指针是否为空。好了,执行测试程序,结果如下:
在这里插入图片描述
可以看到,这里ptr指针最后是一个空指针。
上面三种方式的初始化都是用相应的构造函数来初始化,我们也可以用已经存在的智能指针通过赋值来初始化另一个智能指针。
4)用已经存在的智能指针通过赋值来初始化:

auto_ptr<int> ptr(new int(80));
auto_ptr<int> ptr2;
ptr2 = ptr;  //类模板是重载了=操作符的

同样的,上面提到的所有权问题,在赋值之后ptr的所有权都转移到了ptr2,ptr2拥有int型的对象,对象的值是80,ptr指针悬空。感兴趣的小伙伴也可以写一个测试程序看看赋值之后,ptr是否是空指针。
初始化的注意事项:在初始化智能指针时,我们不该将智能指针指向一个非动态的内存。因为在对象析构时是调用delete的,delete和new是成对使用的,所以在初始化智能指针的时候指向的内存也需要是用new创建出来的。如下所示:

	int i = 10;
	int* p1 = &i;
	auto_ptr<int> ptr(p1);//这是不允许的

3.空的auto_ptr是否需要初始化:

我们都知道在定义一个普通指针时,如果指针暂时没有所指之物,我们都会让这个指针指向空,如:

int *p = NULL;

那么智能指针需要赋值为空吗?对于这个问题,我们可以看一下auto_ptr的源码构造函数的实现:

auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) { }

可以看到在构造函数中的默认值为0,所以像下面这样我们定义一个智能指针,它本身就是指向空的。

auto_ptr<int> ptr;

4.禁止两个智能指针对象指向同一片内存,如下:

	int *p = new int(10);
	auto_ptr<int> ptr1(p);
	auto_ptr<int> ptr2(p);

因为,在两个智能对象析构时,会delete同一块内存两次,两次删除同一个对象在c++标准中是未定义的,所以我们必须禁止将两个智能指针对象指向同一个对象。

5.智能指针作为函数参数时的注意事项:
要知道,智能指针需要作为函数参数时需要十分注意,因为某些行为可能导致智能指针的所有权被转移。我们也知道函数参数一般有传值和传引用两种,下面分别对两种情况解释一下:
1)当函数参数是传值的时候:
我们都知道,将实参按值传递给函数时,都会拷贝一个实参的副本给函数,所以,如果智能指针做实参时,也会有拷贝一个副本,此时就会把实参智能指针的所有权转移到副本上,实参智能指针就成为一个空指针,显然这不是我们想要的,所以在这种行为需要被禁止。
2)在按引用传递时则不会存在上述的拷贝过程,但是我们依然不知道函数对传入的智能指针做了什么操作,也有可能会导致所有权被转移,所以如果要把智能指针作为按引用传递的函数参数时,我们需要声明参数是只读的,用const修饰。

auto_ptr的常用函数

接下来我们来看一下智能指针常用的函数,以及探究一下其源码更能理解上面的内容。
首先看一下其拷贝构造函数:
1)拷贝构造函数

template<typename _Tp1>
auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) { }

这里我们先提前介绍一下release函数的用途,release函数是返回auto_ptr指向的那个对象的内存地址,并释放对这个对象的所有权。所以在拷贝构造函数中,_a.release()的返回值会赋值给_M_ptr,即a指向的对象内存地址会赋值给_M_ptr,然后a会释放所有权。所以在上面初始化的时候也说明了在调用拷贝构造函数时会存在所有权转移。
2)get()函数

get() const throw() { return _M_ptr; }

get函数也很简单,我们上面的例子中也有用过,其用途就是返回智能指针对象的内存地址。示例程序:

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

int main()
{
	int *p = new int(20);
	cout << p << endl;
	auto_ptr<int> ptr(p);
	cout << ptr.get() << endl;
	return 0;
}

运行程序,结果如下:
在这里插入图片描述
3.release函数:

  element_type*
      release() throw()
      {
	element_type* __tmp = _M_ptr;//保存对象内存地址
	_M_ptr = 0;//原本的对象内存地址置空,释放所有权
	return __tmp;//返回对象内存地址
      }

通过源码可以很清晰的看出release函数的作用就是返回对象内存地址并释放所有权。
4.reset函数

void reset(element_type* __p = 0) throw()
      {
	if (__p != _M_ptr)
	  {
	    delete _M_ptr;
	    _M_ptr = __p;
	  }
      }

该函数作用是重新设置智能指针的指向,可以理解为重新对智能指针赋值。如下:`

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

int main()
{
	int *p = new int(20);
	auto_ptr<int> ptr(p);
	cout << *ptr << endl;
	ptr.reset(new int(10));
	cout << *ptr << endl;
	return 0;
}

测试结果如下:
在这里插入图片描述
5.重载=运算符:

template<typename _Tp1>
        auto_ptr&
        operator=(auto_ptr<_Tp1>& __a) throw()
        {
	  reset(__a.release());
	  return *this;
	}

首先可以看到重载运算符接收的参数是智能指针对象,所以赋值运算不允许将一个普通指针指直接赋给auto_ptr。以下面的例子来解析一下赋值的过程:

	auto_ptr<int> a(new int(10));
	auto_ptr<int> b = a;

首先a会调用release函数,释放了对象了所有权,release返回值是对象的地址,把返回来的地址作为参数传入reset函数,即设置了b的指向为a返回来的那块内存的地址,返回*this,至此赋值过程结束。
好了,auto_ptr就到这,后面会带来其他智能指针的文章。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++智能指针之auto_ptr详解(有源码有实例) 的相关文章

随机推荐

  • LWIP学习笔记(2)---ARP简析

    ARP协议概述 即地址解析协议 用于实现从 IP 地址到 MAC 地址的映射 即询问目标IP对应的MAC地址 ARP分组格式 以太网目的地址 MAC 以太网源地址 MAC 帧类型 硬件类型 协议类型 OP 发送端目的地址 发送端 地址 目的
  • Selenium 高频面试题及答案

    1 什么是 Selenium 它用于做什么 Selenium 是一个用于自动化测试的开源框架 它提供了多种工具和库 用于模拟用户在不同浏览器和操作系统上的行为 并且可用于测试网页应用程序 2 Selenium WebDriver 和 Sel
  • 2023前端面试题及答案整理(CSS)

    盒模型 标准盒模型 W3C标准 一个块的总宽度 内容宽度 margin 左右 padding 左右 border 左右 怪异盒模型 IE标准 一个块的总宽度 width 包含 padding 和 border margin 左右 怪异盒模型
  • C++并发编程框架Theron(8)——Theron中包含的类(二)

    1 前言 本篇文章主要接着上一篇来介绍Theron框架库中包含的类 上一篇中主要介绍了Theron下Actor Address AllocatorManager和Catcher类 在本篇文章中我会相继介绍DefaultAllocator E
  • 解决vscode找不到arduino esp8266头文件

    用Arduino IDE写ESP8266没有代码补全 不能跳转查看头文件 个人觉得这是最难受的 vscode装上Microsoft的arduino扩展后 有时候会找不到头文件 刚开始自己傻傻的一个个往includePath里面添加 后来在引
  • 决策树(Decision Tree)简介

    决策树 Decision Tree 及其变种是另一类将输入空间分成不同的区域 每个区域有独立参数的算法 决策树分类算法是一种基于实例的归纳学习方法 它能从给定的无序的训练样本中 提炼出树型的分类模型 树中的每个非叶子节点记录了使用哪个特征来
  • 李宏毅机器学习课程第4讲:Estimator

    李宏毅机器学习课程第4讲 Estimator 味儿太冲了 开头又是宝可梦哈哈哈哈哈 不过听了这一讲 我对于过拟合和欠拟合的理解更深刻了 李老师从Bias和Variance两个角度进行了解释 对 于 这 样 一 个
  • linux centos7 centos8 Rocky8.8 Rocky9.2 sed错误sed: -e expression #1, unknown option to `s‘解决办法

    报错如下 sed e expression 1 char 13 unknown option to s 需要替换的行为 monitor url http 192 168 25 100 8443 rest 查询资料得知 报错是因为替换的字符串
  • 高等数学(工本)填空题

    1 2 3 4 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 4
  • Error: listen EADDRINUSE: address already in use 127.16.20.217:8090

    端口占用npm run staert无效 今天遇到一个问题 就是在提交代码时怕把本地端口号提交到远程 然后将已经启用的端口设置为localhost 待提交完代码后再刷新页面 结果一直更新不出来 然后重启代码 终端报 Error listen
  • 怎么上传代码到GitHub

    怎么上传代码到GitHub 2021 9 我们想要把代码上传到github上面用git管理 但是怎么操作呢 首先在本地电脑安装git 和在github上面注册账号 git下载地址 git官网链接 然后我们要知道有二种方式可以配置操作 一种是
  • 机器学习有监督学习之--回归

    一 引言 本材料参考Andrew Ng大神的机器学习课程 http cs229 stanford edu 以及斯坦福无监督学习UFLDL tutorial http ufldl stanford edu wiki index php UFL
  • Linux 多线程调试(内存占用、死循环、CPU占用率高……)

    你的软件在某个时刻停止服务 CPU占用达到100 这种问题一个可能的原因是产生了死循环 假设程序某处存在潜在的死循环 并在某种条件下会引发 本文以一个示例来定位出现死循环的位置 当程序某处存在死循环 通常定位问题及缩小范围的方法是 在可疑的
  • 数据库Sharding集群:扩容问题解决方案

    数据库Sharding集群扩容问题方案 MySQLSharding集群一般按照用户id进行哈希分区 这里面存在两个问题 1 集群的容量不够怎么办 2 单个用户的数据量太大怎么办 一 问题一 对于第1个问题 MySQLSharding集群往往
  • UART与TTL

    一 首先UART和TTL完全就是两码事 UART是通用异步收发传输器 Universal Asynchronous Receiver Transmitter 通常称作UART 是一种串行异步收发协议 TTL电平信号规定 5V等价于逻辑 1
  • DataOutputStream 类与BufferedOutputStream类的区别是什么

    DataOutputStream 类与Buffere dOutputStream类的区别是什么 DataOutputStream dataout new DataOutputStream new FileOutputStream file
  • Google Earth Engine(GEE) 01-中输入提示快捷键Ctrl+space无法使用的问题

    Google Earth Engine GEE 01 中输入提示快捷键Ctrl space无法使用的问题 GEE中 Ctrl space组合键用于代码输入快捷提示 能够提高编码的准确度和速度 但是 windows系统默认Ctrl space
  • Windows下Jenkins的运行环境由Java8 升级为Java11

    开源 Devops 工具 Jenkins 在官方博客宣布 从 6 月 28 日发布的 Jenkins 2 357 和将于 9 月发布的 LTS 版本开始 Jenkins 需要 Java 11 才能使用 将放弃 Java 8 步骤 1 安装j
  • STM32CUBEMX配置教程(一)基础配置

    STM32CUBEMX配置教程 一 基础配置 基于STM32H743VI 使用STM32CUBEMX两年了 始终觉得这个工具非常的方便 但因为不是经常使用 导致有些要点总是会有些遗忘 因此写下这一系列教程以供记忆 顺便让我这个大萌新给广大小
  • c++智能指针之auto_ptr详解(有源码有实例)

    前言 内存泄漏大概是每一个c c 程序员最深恶痛绝的问题 因为大部分此类问题都是令广大c程序员很抓狂 掉头发的疑难杂症 而内存泄漏的根本原因就是指针的使用不当引起的 例如指针指向的内存没有释放 导致产生了程序无法控制的内存块 而随着程序不断