Learn C++学习笔记:第M章—智能指针和移动语义

【前言】

智能指针和移动语义是迄今为止,最难理解的两个概念。网上查的资料也都解释的要么太高深,要么泛泛为止,反复阅读了好几遍才恍然大悟,领会之后发现其实也没有那么难。

智能指针为什么和移动语义有关系

智能指针的概念出现其实是非常合理,毕竟有的时候会用new开辟空间,如果这块地址不及时释放,就会造成内存泄漏程序崩溃,所以如果这个指针可以自己在该释放的时候释放,不用人为管理那么多指针,岂不美哉,这就是智能指针。

那么移动语义是什么?它和智能指针是啥关系?
这个问题的关键就在于,指针可能被用于赋值,传递等,一个智能指针A它把值赋值给另一个指针B,然后A的生命结束了并且释放了自己管理的内存区域,那么B指针的地址岂不是指向了未知区域?
所以智能指针之间的赋值不是一个简单的直接=赋值完事儿,比如A赋值完,那么B其实也指向那块内存,两个指针都指向内存,并且都会“自动”释放,这就是很危险的事情,所以在A赋值给B后,A就不应该在拥有对那块内存的管理权,这就像是A把内存移动给B一样,这就是移动语义。下面详细介绍。

什么是智能指针

先看一段代码

void someFunction()
{
    Resource *ptr = new Resource(); // Resource is a struct or class
    // do stuff with ptr here
    delete ptr;
}

发出灵魂只问:如果在delete ptr这行之前,函数提前返回了,这是不是就内存泄漏了?
那么有什么东西可以实现自动消失呢?局部变量?但是局部变量只能消失自己,没有办法消失第三者比如指针管理的内存。还有什么?对了,就是类。

关于类的最好的事情之一是它们包含析构函数,这些析构函数在类的对象超出范围时自动执行。因此,如果您在构造函数中分配(或获取)内存,则可以在析构函数中对其进行分配,并确保在销毁该类对象时,该内存将被重新分配(无论其是否超出范围,是否被明确删除,等等…)。

这就是智能指针设计的依据、理论基石。智能指针是一个类,也就是说我们把原先new分配的内存变成一个类的变量,并且把释放内存写到析构函数里,这样就不用自己手动释放内存了,是不是觉得很美妙。当然有封装好的这样的智能指针类,但是为了进一步了解智能指针的工作原理,先自己写一个智能指针,一步步完善它,这样更能理解它为什么这么设计。

按照上面的思路,可以实现下面一个版本的智能指针:

#include <iostream>
template <class T>
class Auto_ptr{
	T* m_ptr;
public:
	Auto_ptr(T* ptr = nullptr):m_ptr(ptr){}
	~Auto_ptr(){delete m_ptr;}

	T& operator*() const{return *m_ptr;}
	T* operator->() const {return m_ptr;}
};

class Resource{
public: 
	Resource() {std::cout << "Resource acquired\n";}
	~Resource(){std::cout<< Resource destoryed";}
	void sayHi() {std::cout << "Hi!\n";}
};
//使用示范
void sample(){
	Auto_ptr1<Resource> ptr(new Resource());   //创建智能指针
	if (true) return;
}

调用sample()函数后将会打印:

Resource acquired
Resource destroyed

可以发现,上面简单的一个类,已经实现了智能指针的基本功能!
那么接下来其实很自然的需要完善它,上面的功能有什么缺陷呢?

移动语义

  • 如果指针传递的话怎么办?两个智能指针都拥有内存的所有权,一个释放了,另外一个在释放时候怎么办?

指针传递有三种方式可选,按值传递、按地址传递、和引用传递。具体可以复习这篇内容:指针值传递、地址传递和引用传递
逐个进行分析,

  • 按值传递的话如上面所说,第二个智能指针将变成悬空指针;
  • 按引用传递的话,和按值传递一样,可能会变成悬挂引用;
  • 按地址传递,首先我们还得记得手动释放这个地址,这样智能指针就不智能了,其次就是地址里面对应的内容可能已经被提前释放了。

所以针对智能指针的复制问题,跟经典的变量复制问题不一样,他又一个更贴切的表达:**移动,将指针的所有权从源目标转移到目标对象。**这就是为什么移动语义和智能指针一起出现。
移动语义意味着类将转移对象的所有权,而不是进行复制。比如智能指针A将值赋值给智能指针B时,A同时失去对指针管理内容的所有权,也就是A已经指向空了,所以A释放的时候对之前管理的内存毫无影响。就不会存在悬空指针的问题了。
根据上面的设计思想可以知道,需要重写智能指针类的复制构造函数。

#include <iostream>
template <class T>
class Auto_ptr{
	T* m_ptr;
public:
	Auto_ptr(T* ptr = nullptr):m_ptr(ptr){}
	~Auto_ptr(){delete m_ptr;}

//-----------------New  Add -----------------
	Auto_ptr(Auto_ptr& a){
		m_ptr = a.m_ptr;
		a.m_ptr = nullptr;
	}
	Auto_ptr& operator=(Auto_ptr& a){
		if(&a == this) return *this;
		delete m_ptr;
		m_ptr = a.m_ptr;
		a.m_ptr = nullptr;
		return *this;
	}
//-----------------New  Add -----------------

	T& operator*() const{return *m_ptr;}
	T* operator->() const {return m_ptr;}
};

class Resource{
public: 
	Resource() {std::cout << "Resource acquired\n";}
	~Resource(){std::cout<< Resource destoryed";}
	void sayHi() {std::cout << "Hi!\n";}
};

int main()
{
	Auto_ptr<Resource> res1(new Resource());
	Auto_ptr<Resource> res2; // Start as nullptr

	std::cout << "res1 is " << (res1.isNull() ? "null\n" : "not null\n");
	std::cout << "res2 is " << (res2.isNull() ? "null\n" : "not null\n");

	res2 = res1; // res2 assumes ownership, res1 is set to null

	std::cout << "Ownership transferred\n";

	std::cout << "res1 is " << (res1.isNull() ? "null\n" : "not null\n");
	std::cout << "res2 is " << (res2.isNull() ? "null\n" : "not null\n");

	return 0;
}

要留意的是上面的复制函数中,形参用的是引用,因为里面要更改实参,如果不使用引用,则无法修改。
本篇基本介绍了一下智能指针的为什么、是什么和怎么样,和移动语义的为什么、是什么。下面会分几篇详细介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值