C++11 谈谈shared_ptr

C++11 谈谈shared_ptr(细节)

个人用!十分主观!仅供参考!

shared_ptr是C++11中加入的一种智能指针(其实并不够智能),其作用就是帮助我们管理在堆中开辟的空间,避免野指针等众多内存管理不当造成的问题。

重点:智能指针会自动的给我们释放开辟的内存空间

实际上,每种智能指针都是以类模板的方式实现的,shared_ptr

shared_ptr使用了引用计数机制,也就是其类内维护了一个计数count。其之所以叫做shared,多个shared_ptr可以共同使用同一块堆内存,一旦多了一个新的shared_ptr使用了这块内存,这个计数count就会+1。

初始化

//显式
shared_ptr<int> p1;//默认初始化为nullptr,默认构造
shared_ptr<int> p2(new int(10));//使用指针初始化,含参构造
shared_ptr<int> p3(p2);//拷贝构造
shared_ptr<int> p4(move(p3));//移动构造

不够智能的智能指针

注意,不能用同一个普通指针初始化超过一个智能指针。

int* p = new int(10);
shared_ptr<int> p1(p);
shared_ptr<int> p2(p);

上面的语句编译不会出错,但运行时会崩掉,因为程序结束时,智能指针会自动释放p1,p2的内存。在此p1,p2的计数都是1,故p这块内存被释放了两次,释放第二次的时候程序崩掉了。

同理,还有下面这种情况:

int* p = new int(10);
shared_ptr<int> p1(p);
delete p;

同样的原因,释放已经释放的内存,程序崩掉了。

因此,智能指针的使用要和传统的指针用法相区别开。

shared_ptr模板类提供的成员方法

参考文章C++11 shared_ptr智能指针(超级详细) (biancheng.net)

成员方法名功 能
operator=()重载赋值号,使得同一类型的 shared_ptr 智能指针可以相互赋值。
operator*()重载 * 号,获取当前 shared_ptr 智能指针对象指向的数据。
operator->()重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。
swap()交换 2 个相同类型 shared_ptr 智能指针的内容。
reset()当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。
get()获得 shared_ptr 对象内部包含的普通指针。
use_count()返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。
unique()判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它。
operator bool()判断当前 shared_ptr 对象是否为空智能指针,如果是空指针,返回 false;反之,返回 true。

除此之外,C++11 标准还支持同一类型的 shared_ptr 对象,或者 shared_ptr 和 nullptr 之间,进行 ==,!=,<,<=,>,>= 运算。

主要用到的是use_count()和reset()。最后一个 operator bool(),这是类型转换。

自定义释放规则

对于在类中开辟堆内存的情况:

  1. 可以在类中析构中释放(因为智能指针计数为0时实际就是调用的类的析构);
  2. 可以自定义释放规则;
  3. 可以将类中的指针也定义为智能指针;

先看一段代码:

//定义了一个类,类中开辟了堆内存,用p指向
class tmpA
{
public:
	tmpA():p(new int(10)){}
	int* p;
};
void test()
{
    shared_ptr<tmpA> p1(new tmpA);//创建一个指向tmpA类型的智能指针
	int* p2 = p1->p;//保存p1指向的对象中的p的值
    cout << "对象释放前: *p2 = " << p2 << endl;
	p1.reset();//引用计数-1 =0 , 释放p1指向的空间
	cout << "对象释放后:p1 = " << p1 << endl;//看是否释放成功(是否为0)
    cout << "对象释放后:*p2 = " << *p2 << endl;//看对象中开辟的堆空间是否释放
}

结果:

在这里插入图片描述

这显然没有释放类中的堆内存

  1. 类析构中释放
//定义了一下析构函数
class tmpA
{
public:
	tmpA():p(new int(10)){}
	~tmpA() {
		if (p)
		{
			delete p;
			p = nullptr;
		}
	}
	int* p;
};

结果:

释放了

  1. 自定义释放规则
void test()
{
	shared_ptr<tmpA> p1(new tmpA,
		[](tmpA* cptr) {
			if (cptr->p) {
				delete cptr->p;
				cptr->p = nullptr;
			}
		}
		);//创建一个指向tmpA类型的智能指针,并自定义释放规则(这里用了lambda表达式)
	int* p2 = p1->p;//保存p1指向的对象中的p的值
	cout << "对象释放前: *p2 = " << *p2 << endl;
	p1.reset();//引用计数-1 =0 , 释放p1指向的空间
	cout << "对象释放后:p1 = " << p1 << endl;//看是否释放成功(是否为0)
	cout << "对象释放后:*p2 = " << *p2 << endl;//看对象中开辟的堆空间是否释放
    delete p2;
    p2 = nullptr;
}

结果:

在这里插入图片描述

释放了

  1. 类内指向堆内存的指针定义为智能指针
class tmpA
{
public:
	tmpA():p(new int(10)){}
	shared_ptr<int> p;
};
void test()
{
    shared_ptr<tmpA> p1(new tmpA);//创建一个指向tmpA类型的智能指针
	int* p2 = p1->p.get();//保存p1指向的对象中的p的值(从shared<int>转到int* 要用get()函数)
	cout << "对象释放前: *p2 = " << *p2 << endl;
	p1.reset();//引用计数-1 =0 , 释放p1指向的空间
	cout << "对象释放后:p1 = " << p1 << endl;//看是否释放成功(是否为0)
	cout << "对象释放后:*p2 = " << *p2 << endl;//看对象中开辟的堆空间是否释放
    delete p2;
    p2 = nullptr;
}

结果:

在这里插入图片描述

释放了。

其实也可以用对象中开辟的堆内存初始化一个智能指针,也就是把int p2 改成 shared p2*

这其实也是第三种方法

综上

智能指针的使用要理解其中原理,不然很容易造成过度释放。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值