c++智能指针入门教程

搬运油管视频
通过代码示例讲解智能指针的概念。

概述

智能指针是原始指针的封装,其优点是会自动分配内存,不用担心潜在的内存泄露。
c++有3中不同类型的智能指针:
unique_ptr
weak_ptr
shared_ptr

包含头文件

#include <memory>

unique_ptr

代码示例

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

int main()
{
	unique_ptr<int>unPtr1 = make_unique<int>(25);
	
	system("pause>nul");
}

make_unique创建一个独占智能指针,其值赋25
这个创建的独占智能指针对象就叫unPtr1
引用智能指针指向的地址的值要在前面加*,
若打印unPtr1

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

int main()
{
	unique_ptr<int>unPtr1 = make_unique<int>(25);
	cout << unPtr1 << endl;
	system("pause>nul");
}

在这里插入图片描述
如上图,结果将打印指针指向的地址。
引用指针指向地址的值,在指针前加*

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

int main()
{
	unique_ptr<int>unPtr1 = make_unique<int>(25);
	cout << unPtr1 << endl;
	cout << unPtr1 << endl;
	system("pause>nul");
}

在这里插入图片描述
*unPtr1的值为25

unique_ptr唯一指针有一个非常重要的特性就是它们不能共享。就是unique_ptr已经指向的地址,其他指针不能再指向。
如下示例:

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

int main()
{
	unique_ptr<int>unPtr1 = make_unique<int>(25);
	unique_ptr<int>unPtr2 = unPtr1;
	system("pause>nul");
}

这样的话,会报错
在这里插入图片描述
unique_ptr不能共享,但是你可以通过move将其对象所有权转给其他指针,move是标准库里的操作函数,会将被操作对象的值取走,变为空,如下示例

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

int main()
{
	unique_ptr<int>unPtr1 = make_unique<int>(25);
	unique_ptr<int>unPtr2 = move(unPtr1);

	cout << *unPtr2 <<endl;
	system("pause>nul");
}

在这里插入图片描述
unPtr1指向的地址的值25就传给了unPtr2
同时unPtr1变成一个空指针,再访问其地址的值会出错,如下示例

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

int main()
{
	unique_ptr<int>unPtr1 = make_unique<int>(25);
	unique_ptr<int>unPtr2 = move(unPtr1);

	cout << *unPtr2 <<endl;
	cout << *unPtr1 <<endl;
	system("pause>nul");
}

在这里插入图片描述
简单介绍一下构造函数和析构函数
构造函数:类对象被创建时会调用
析构函数:类对象销毁时会调用
现在创建一个MyClass类来说明为什么智能指针能自动分配内存,防止内存泄露,MyClass类的构造函数被调用时(创建对象时)打印“constructor invoked”,析构函数被调用时(销毁对象时)打印"Destructor invoked"
代码示例如下:

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

class MyClass{
	public:
		MyClass(){
			cout<<"Constructor invoked"<<endl;
		}
		~MyClass(){
			cout<<"Destructor invoked"<<endl;
		}
}

int main()
{
	//创建一个唯一智能指针unPtr1,通过make_unique创建一个空对
	//MyClass类对象返回给unPtr1
	unique_ptr<MyClass>unPtr1 = make_unique<MyClass>();
	
	system("pause>nul");
}

在这里插入图片描述
运行结果如上图,看到只有构造函数被调用,
唯一指针unique_ptr在其作用域也就是两个花括号之间结束后会被销毁,
这里由于最后一句代码暂停导致一直没有执行到末尾,现在额外加上一个花括号作用域看看效果,代码更改如下:

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

class MyClass{
	public:
		MyClass(){
			cout<<"Constructor invoked"<<endl;
		}
		~MyClass(){
			cout<<"Destructor invoked"<<endl;
		}
}

int main()
{
	//创建一个唯一智能指针unPtr1,通过make_unique创建一个空对
	//MyClass类对象返回给unPtr1
	{
	unique_ptr<MyClass>unPtr1 = make_unique<MyClass>();
	}
	
	system("pause>nul");
}

在这里插入图片描述
当额外加上一个作用域后,在unique_ptr=…这一行调用构造函数,在额外作用域花括号结束时调用了析构函数

shared_ptr

共享指针不像唯一指针,可以被多个所有者共享
我们来演示一下它时如何工作的
如何创建一个共享指针

shared_ptr<MyClass>shPtr1 = make_shared<MyClass>();

一个关于共享指针有趣的是,它可以返回所有者的计数
通过共享指针的成员函数.use_count()可以返回其所有者个数,代码示例如下:

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

class MyClass{
	public:
		MyClass(){
			cout<<"Constructor invoked"<<endl;
		}
		~MyClass(){
			cout<<"Destructor invoked"<<endl;
		}
}

int main()
{
	//创建一个共享指针
	shared_ptr<MyClass>shPtr1 = make_shared<MyClass>();
	cout<<"shared count: "<<shPtr1.use_count()<<endl;

	system("pause>nul");
}

这样的话将会打印make_shared创建的共享指针的所有者的个数,这里只有1,看下运行结果:
在这里插入图片描述
如果现在为这个内存位置创建一个额外的所有者

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

class MyClass{
	public:
		MyClass(){
			cout<<"Constructor invoked"<<endl;
		}
		~MyClass(){
			cout<<"Destructor invoked"<<endl;
		}
}

int main()
{
	//创建一个共享指针
	shared_ptr<MyClass>shPtr1 = make_shared<MyClass>();
	cout<<"shared count: "<<shPtr1.use_count()<<endl;
	//再创建一个共享指针指向shPtr1指向的内存地址
	shared_ptr<MyClass>shPtr2 = shPtr1;
	cout<<"shared count: "<<shPtr1.use_count()<<endl;

	system("pause>nul");
}

在增加所有者shPtr2后,共享指针shPtr1的引用计数将由1变为2,运行效果如下:
在这里插入图片描述
那么对于这个共享的智能指针,它的内存什么时候会被回收呢?
问题的答案是,当没有指针指向这个内存地址时,它就会被回收。
每个所有者都在其作用域结束后被销毁。
给出一个示例如下:

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

class MyClass{
	public:
		MyClass(){
			cout<<"Constructor invoked"<<endl;
		}
		~MyClass(){
			cout<<"Destructor invoked"<<endl;
		}
}

int main()
{
	//创建一个共享指针
	shared_ptr<MyClass>shPtr1 = make_shared<MyClass>();
	cout<<"shared count: "<<shPtr1.use_count()<<endl;
	{
		//再创建一个共享指针指向shPtr1指向的内存地址
		shared_ptr<MyClass>shPtr2 = shPtr1;
		cout<<"shared count: "<<shPtr1.use_count()<<endl;
	}
	cout<<"shared count: "<<shPtr1.use_count()<<endl;
	
	system("pause>nul");
}

这样的话将会发生什么现象呢?
第一次打印cout内存只引用一次,打印1
第二次在中间的花括号又增加所有者shPtr2,打印2
第三次在中间的花括号结束后,shPtr2的作用域结束了被销毁了,引用又只有shPtr1了,又打印1,
运行结果如下:
在这里插入图片描述
同时可以看到,尽管创建了两个共享指针但构造函数只被调用了一次,因为第二个shPtr2指向的是同一个内存地址。
现在相比上面代码再加一个额外的作用域把unPtr1也包起来

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

class MyClass{
	public:
		MyClass(){
			cout<<"Constructor invoked"<<endl;
		}
		~MyClass(){
			cout<<"Destructor invoked"<<endl;
		}
}

int main()
{
	//创建一个共享指针
	{
		shared_ptr<MyClass>shPtr1 = make_shared<MyClass>();
		cout<<"shared count: "<<shPtr1.use_count()<<endl;
		{
			//再创建一个共享指针指向shPtr1指向的内存地址
			shared_ptr<MyClass>shPtr2 = shPtr1;
			cout<<"shared count: "<<shPtr1.use_count()<<endl;
		}
		cout<<"shared count: "<<shPtr1.use_count()<<endl;
	}
	
	system("pause>nul");
}

那么将发生
shared_ptrshPtr1 = make_shared();创建第一个共享指针shPtr1
cout<<"shared count: "<<shPtr1.use_count()<<endl; 此时引用次数为1,同时会调用一次构造函数
{
//再创建一个共享指针指向shPtr1指向的内存地址
shared_ptrshPtr2 = shPtr1;
cout<<"shared count: "<<shPtr1.use_count()<<endl;
}
进入到该作用域又创建了shPtr2又引用了shPtr1这个地址,use_count变为2,这个作用域结束时,这个智能指针内存地址仍然有shPtr1这个所有者,所以直到
cout<<"shared count: "<<shPtr1.use_count()<<endl;
} //shPtr1的作用域结束
system(“pause>nul”);
}

此时shPtr2的作用域结束,那么use_count又变为1,直到这个花括号,shPtr1的作用域也结束,此时内存回收,调用析构函数。
运行结果如下:
在这里插入图片描述
同时再提一下,shared_ptr在作为形参时存在拷贝,每当这个函数被调用一次就会增加1个us_count,当函数调用结束时,use_count又会恢复

weak_ptr

弱指针和共享指针的区别:给共享指针分配一个特定内存位置时,将增加该内存位置所有者的数量。但如果你把相同的内存位置赋值给一个弱指针,这不会增加它的所有者数量。
我们使用弱指针来观察内存中的对象 ,但弱指针不会让对象保持活动。
用代码示例来演示一下刚刚说的

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

class MyClass{
	public:
		MyClass(){
			cout<<"Constructor invoked"<<endl;
		}
		~MyClass(){
			cout<<"Destructor invoked"<<endl;
		}
}

int main()
{
	//创建一个弱指针
	weak_ptr<int> wePtr1;
	{
		//创建一个共享指针,并赋值25
		shared_ptr<int> shPtr1 = make_shared<int>(25);
		//让弱指针指向共享指针指向的内存位置
		wePtr1 = shPtr1;
	}	
	system("pause>nul");
}

现在我们调试这个程序,打断点,单步运行
在这里插入图片描述
此时wePtr1仍然是空的
创建弱指针后,我们进入下面的作用域
在这里插入图片描述
运行到这一步时,共享指针指向一个强引用的内存位置,这个位置储存的值是25
接下来这行代码中,将共享指针赋给弱指针,弱指针指向同样的地址
在这里插入图片描述
在这里插入图片描述
strong ref,出作用域之前有强引用
当离开上图中21行这个作用域时
在这里插入图片描述
在这里插入图片描述
出了作用域之后,只有weak ref弱引用了,但弱指针指向内存储存的值仍为25
上图中另一个信息是expired,过期了,这个内存位置所有的所有者都被销毁了,只剩下一个引用。weak_ptr仍然保存这这个地址,但这个地址已经过期了。weak_ptr不计入内存位置的所有者,当内存位置的最后一个所有者被销毁,弱指针也将过期。

  • 6
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

wujiangzhu_xjtu

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

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

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

打赏作者

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

抵扣说明:

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

余额充值