37、智能指针的分析

1、永恒的话题

  • 内存泄漏(臭名昭著的Bug)
    — 动态申请堆空间,用完不还
    — C++语言中没有垃圾回收的机制
    — 指针无法控制所指堆空间的生命周期
#include <iostream>
#include <string>
using namespace std;

class Test
{
private:
	int i;
public:
	Test(int i)
	{
		this->i = i;
	}
	int value()
	{
		return i;
	}
	~Test()
	{

	}
};
int main()
{
	for (int i = 0; i < 5; i++)
	{
		Test* p = new Test(i);
		cout << p->value() << endl;

	}
	return 0;
}

在这里插入图片描述
这个程序的问题在于在代码的27行,每for循环一次,就new一个对象,但是只用一个指针去指向这个对象,没释放内存,但是指针马上又指向了另一段内存,这就造成了内存泄漏。

2、深度的思考

  • 我们需要什么
    — 需要一个特殊的指针
    — 指针生命周期结束时主动释放堆空间
    — 一片堆空间最多只能由一个指针标识   //避免多次释放
    — 杜绝指针运算和指针比较

3、智能指针分析(使用对象代替指针)

  • 解决方案
    — 重载指针特征操作符(->*
    — 只能通过类的成员函数重载
    — 重载函数不能使用参数
    —只能定义一个重载函数
#include <iostream>
#include <string>
using namespace std;

class Test
{
private:
	int i;
public:
	Test(int i = 0)
	{
		cout << "Test(int i = 0)" << endl;
		this->i = i;
	}
	int value()
	{
		return i;
	}
	~Test()
	{
		cout << "~Test()" << endl;
	}
};

class Pointer
{
private:
	Test* mp;
public:
	Pointer(Test* p = NULL)
	{
		mp = p;
	}
	Test* operator->()
	{
		return mp;
	}
	Test& operator*()
	{
		return *mp;
	}
	~Pointer()
	{
		delete mp;
	}
};

int main()
{
	for (int i = 0; i < 5; i++)
	{
		Pointer p = new Test(i);    //其实赋值符号的左右两边都是定义对象,左边是对成员变量进行初始化,右边是new 一段堆上的空间,两个直接对等,相当于用mp就指向堆空间。
		cout << p->value() << endl;
	}
	return 0;
}

在这里插入图片描述

  • 重点
    1、->操作符的重载 返回值类型类指针
    2、*操作符的重载 返回值类型类引用

从结果我们可以发现,指针指向的内存被释放了,不会造成内存泄漏。智能指针真的强。

  • 分析
    我们分析一下智能指针的原理:因为析构发生在对象生命周期结束的时候,我们的Pointer p是一个栈对象,栈上的对象会生命周期结束的时候自动析构。每一次循环结束后,p都会被析构,(p的作用域就是那个for循环的花括号{ },每次花括号循环一次结束p的生命周期就结束了)从而调用~Pointer()函数里面的delete mp;堆空间被delete,最后调用堆空间的析构函数。总结来说就是:栈的析构使得指针指向的堆空间被delete。

分享一个顺序的程序:

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

class Test
{
private:
	int i;
public:
	Test(int i = 0)
	{
		cout << "Test(int i = 0)" << endl;
		this->i = i;
	}
	int value()
	{
		return i;
	}
	~Test()
	{
		cout << "~Test()" << endl;
	}
};

class Pointer
{
private:
	Test* mp;
public:
	Pointer(Test* p = NULL)
	{
		cout << "Pointer(Test* p = NULL)" << endl;
		mp = p;
	}
	Test* operator->()
	{
		return mp;
	}
	Test& operator*()
	{
		return *mp;
	}
	~Pointer()
	{
		cout << "~Pointer()" << endl;
		delete mp;
	}
};
int main()
{
	for (int i = 0; i < 5; i++)
	{
		Pointer p = new Test(i);
		cout << p->value() << endl;
	}
	return 0;
}

在这里插入图片描述
4、智能指针的再分析

  • 在前面第二点,我们想要智能指针满足以下几个需求
    1、 需要一个特殊的指针
    2、 指针生命周期结束时主动释放堆空间
    3、 一片堆空间最多只能由一个指针标识   //避免多次释放
    4、 杜绝指针运算和指针比较

经过上面的一系列研究,我们解决了上面的第一条和第二条。那如何解决第三条和第四条的需求呢?
答案是:拷贝构造函数 和 赋值操作符重载

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

class Test
{
private:
	int i;
public:
	Test(int i = 0)
	{
		this->i = i;
		cout << "Test(int i = 0)" << endl;
	}
	int getI()
	{
		return i;
	}
	~Test()
	{
		cout << "~Test()" << endl;
	}
};
class Pointer
{
private:
	Test* mp;
public:
	Pointer(Test* p = NULL)
	{
		mp = p;
	}
	Pointer(const Pointer& obj)
	{
		mp = obj.mp;
		const_cast<Pointer&>(obj).mp = NULL;
	}
	Pointer& operator=(Pointer& obj)
	{
		if (this != &obj)
		{
			delete mp;
			mp = obj.mp;
			const_cast<Pointer&>(obj).mp = NULL;
		}
		return *this;
	}
	Test* operator->()
	{
		return mp;
	}
	Test& operator*()
	{
		return *mp;
	}
	bool isNULL()
	{
		return (mp == NULL);               //判断是否为空指针
	}
	~Pointer()
	{
		delete mp;
	}
};

int main()
{
	Pointer p1 = new Test(0);

	cout << p1->getI() << endl;

	Pointer p2 = p1;

	cout << p1.isNULL() << endl;
	cout << p2->getI() << endl;


	return 0;
}

在这里插入图片描述

  • 分析
    我们先分析一下第3点需求:一片堆空间最多只能由一个指针标识,我们是如何实现的呢?
    我们借助 拷贝构造函数 和 赋值操作符重载。拷贝构造函数为Pointer(const Pointer& obj),在里面我们先进行指针地址的赋值,然后把被赋值对象 obj 的指针给置为空。因为 obj 是 const 类型,不能作为左值使用,所以需要一个 const_cast 的强制类型转换。

对于 赋值操作符重载 要注意四点内容:
— 返回值类型为类引用
— 参数类型为 const 类引用
— 加 if 语句防止自赋值
— 返回类本身
所以返回值类型为 Pointer 引用,参数类型为 const Pointer&。第一句语句防止自赋值, if (this != &obj)。然后delete mp是为了防止 new 第二个对象,把初始化的给delete了。mp = obj.mp; const_cast<Pointer&>(obj).mp = NULL;这两句语句和拷贝构造函数里面是一样的。

综上,我们完成了第3点需求:一片堆空间最多只能由一个指针标识。主要原理就在于一旦出现多个我们就让上一个指向的指针指向 NULL

第四点需求只要我们不重载运算符,那么它就没有与这些操作符相匹配的运算符。

综上所诉,需求全部完成。唯一的缺陷在于上面的智能指针只能指向Test这个固定的类型,没办法指向其他的类型。后续学模板技术就可以指向其他的类型。

5、军规

智能指针的使用军规:只能用来指向堆空间中的对象或者变量,不能使用智能指针来指向栈空间中的对象或者变量。

  • 小结
    — 指针特征操作符(->*)可以被重载
    — 重载指针特征符能够使用对象代替指针
    — 智能指针只能用于指向堆空间中的内存
    — 智能指针的意义在于最大程度的避免内存问题

▲▲▲一直以来的疑惑(已解决)
Pointer p = new Test(3);把它改成 Pointer p(new Test(3))方便更好的理解,先调用成员函数的构造函数,把这个当做Pointer 对象构造函数里面的参数,可以达到一一对应的情况。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值