深入学习C++——44智能指针

11 篇文章 0 订阅

深入学习C++——44智能指针

我们都知道使用new在堆上分配内存,使用delete释放内存,因为它不会自动释放内存。智能指针本质上是一个原始指针的包装类,当创建一个智能指针,它会调用new并为其分配内存,基于这个智能指针,这些内存会在某一时刻自动释放。下面介绍三种智能指针

使用智能指针的时候要包含memory头文件

作用域指针unique_ptr

unique_ptr是作用域指针,超出作用域时会被销毁,然后调用delete。
我们不能复制一个unique_ptr,因为如果复制一个unique_ptr,那么他们会指向同一个内存块。如果其中一个死了,它会释放那段内存,而指向同一块内存的另一个指针就会指向已经被释放的内存。

我们首先创建一个类来理解智能指针,类中只包含构造和析构函数

class Entity
{
public:
	Entity()
	{
		std::cout << "Created Entity" << std::endl;
	}

	~Entity()
	{
		std::cout << "Destroyed Entity" << std::endl;
	}
    
	void Print() {}
};

在特定的作用域下创建一个unique_ptr:在main中创建一对大括号,括号内即为空作用域。在里面使用unique_ptr来分配Entity:

int main() 
{
	{
		std::unique_ptr<Entity> entity = new Entity(); //错误
	}
}

这样构造会报错,因为unique_ptr的构造函数是explicit的,需要显式调用构造函数,没有构造函数的隐式转换,所以应该:

int main() 
{
	{
		std::unique_ptr<Entity> entity(new Entity()); //正确
	}
}

不过更推荐使用下面这种方法!原因是出于异常安全。如果构造函数碰巧抛出异常,使用make_unique会保证你最终得到的不是没有引用的悬空指针,从而造成内存泄漏。

int main() 
{
	{
		std::unique_ptr<Entity> entity = std::make_unique<Entity>();
	}
}

然后就可以跟原始指针一样地使用智能指针,比如使用箭头操作符调用函数:

int main() 
{
	{
		std::unique_ptr<Entity> entity = std::make_unique<Entity>();
		entity->Print();
	}
}

单步调试,可以看到程序在进入作用域{时输出Created Entity,出作用域}时输出Destroyed Entity,即为智能指针的自动创建与销毁。

如果需要拷贝或共享这个指针,使得这个指针可以被传递到一个函数中或一个类中,unique_ptr将不可用。如果试图复制一个unique_ptr:

int main() 
{
	{
		std::unique_ptr<Entity> entity = std::make_unique<Entity>();
		std::unique_ptr<Entity> e0 = entity;
	}
}

这样会报错,因为在unique_ptr的定义中,删除了拷贝构造函数和拷贝构造操作符,因为这是不被允许的。这是为了防止你跳到大坑里,因为其中一个unique_ptr死了,这个堆分配对象的底层内存会被释放,另一个unique_ptr会指向这个不存在的内存。所以出现了共享指针shared_ptr:

共享指针shared_ptr

共享指针shared_ptr更牛逼一点,shared_ptr实现的方式实际上取决于编译器和你在编译器中使用的标准库,在大多数情况下,它使用的是引用计数。引用计数基本上是一种方法,可以跟踪你的指针有多少个引用,一旦引用计数达到0,它就会被删除。如果我创建了一个shared_ptr,又创建了另外一个shared_ptr来复制他,此时的引用计数就是2,当第一个shared_ptr死了,引用计数减1变成1,当最后一个shared_ptr也死了,引用计数变为0,这个指针也就会被销毁内存被释放。

但是不要这样写!!!

int main() 
{
	{	
		std::shared_ptr<Entity> entity(new Entity());
	}
}

在unique_ptr中不直接调用new的原因是因为异常安全,而在shared_ptr中有所不同。因为shared_ptr需要分配另一块内存,叫做控制块,用来存储引用计数。如果使用new创建一个Entity然后传递给shared_ptr构造函数,那么它必须做两次内存分配:先做一次new Entity的分配,然后是shared_ptr的控制内存块的分配。使用make_shared就可以将两个步骤合起来:

int main() 
{
	{	
		std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
	}
}

shared_ptr可以被复制。

int main() 
{
	{	
		std::shared_ptr<Entity> entity = std::make_shared<Entity>();
		std::shared_ptr<Entity> e0 = sharedEntity;
	}
}

我们更改一下main函数,创建两个作用域来演示:

int main()
{
	{//作用域1
		std::shared_ptr<Entity> e0;
		{//作用域2
			std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
			e0 = sharedEntity;
		}
		// 此时SharedEntity已经死亡,但是e0还存活(引用计数为1),所以这里没有调用析构函数
	}
	//这里e0也死亡了(引用计数为0),此时调用析构函数。
}

单步调试,进入作用域1时输出Created Entity,此时出作用域2,并没有析构Entity,因为e0还存活,并且持有对该Entity的引用。出作用域1,输出Destroyed Entity,所有的引用此时都消失。

弱指针weak_ptr

将一个shared_ptr赋值给另一个shared_ptr时会增加引用计数,但是将一个shared_ptr赋值给一个weak_ptr时不会增加引用计数。这常用于:如果你不想要Entity的所有权,例如你在排序一个Entity列表,你不关心他们是否有效,只需要存储一个他们的引用,这时就可以使用weak_ptr。可以询问weak_ptr底层对象是否还存活,但它不会保持底层对象存活,因为它不会增加引用计数。

int main()
{
	{//作用域1
		std::weak_ptr<Entity> e0;
		{//作用域2
			std::shared_ptr<Entity> sharedEntity = std::make_shared<Entity>();
			e0 = sharedEntity; // 弱指针不会引用计数
		}//出作用域2的时候就调用了析构函数
		//此时weak_ptr为一个无效指针
	}
}

综上,使用智能指针可以使内存管理自动化,防止忘记调用delete而造成内存泄露。优先选用unique_ptr,如需在作用域之间复制共享就使用shared_ptr。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

范子琦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值