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

前言

内存泄漏大概是每一个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就到这,后面会带来其他智能指针的文章。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值