C++基础_智能指针与动态内存(一)shared_ptr类

1、程序执行时的内存空间分配

C/C++程序所占用的内存除了文字常量区(存放常量字符串)和程序代码区(存放函数体的二进制代码),主要是三个部分:

1、栈内存(stack) 由编译器自动分配释放,存放定义在函数内的非static对象。如函数内的局部变量、函数参数、返回数据、返回地址等,当这些对象离开作用域后会被自动释放。

2、静态内存 由编译器自动分配释放,保存局部static对象,类static数据成员以及定义在任何函数之外的变量。包括:
1)已初始化的全局变量和静态变量。
2)未初始化的全局变量和静态变量。
3)常量数据区。

3、堆内存,自由空间(heap) 保存动态分配的对象,必须显式的创建和销毁。
动态内存,就是由程序员从堆中根据需要申请的一块内存。动态分配的对象的生存期与他们在哪里创建是无关的,只有当显式的被释放时,这些对象才会销毁。

2、为什么需要动态内存?

主要有以下三种原因:
1)程序不知道自己需要使用多少对象。

2)程序不知道所需对象的准确类型。

3)程序需要在多个对象间共享数据(共享状态)。

对于第一种原因,很好的例子就是容器类。c++中原始数组,必须在声明的时候就给出数组的大小,很不灵活,动态数组就是实现随时添加元素并且不会造成资源的浪费。对于第三个原因,因为动态内存是由程序员分配释放,不会被编译器销毁,所以可以通过指向动态内存的指针在对象共享数据。

3、为什么要引入智能指针?

在C++中,动态内存的管理是通过一对运算符来完成的:
new:在动态内存中为对象分配空间并返回一个指向该对象的指针,可以选择对对象进行初始化。
delete:接受一个动态对象的指针,并销毁该对象,并释放与之关联的内存。

动态内存的使用很容易出问题,常见的就有以下三个问题:

1)忘记delete内存。忘记释放动态内存会导致内存泄漏,这种内存永远不会被归还给自由空间。查找这种错误很难,通常只有当内存被耗尽了才能检测到这种问题。

2)使用已经释放掉的对象。释放掉内存和指针指向的内容已经置空,但是指针本身(动态内存的地址)仍然存在,这种指针叫做空悬指针,即指向一块曾经保存数据对象但是现在已经无效的内存的指针。而且由于动态内存可能有多个指针指向相同的内存,即使delete之后重置指针,也只是对这个指针有效,其他的空悬指针仍然存在。
例如:

int *p(new int(52));//p指向动态内存
auto q = p;//p和q指向相同的内存
delete p;//p和q均变为无效
p = nullptr;//重置p

3)同一块内存释放两次。当有两个指针指向相同的动态内存分配对象时,可能发生这种错误,我们先对其中一个指针delete,归还对象的内存给自由空间,然后再delete第二个指针,自由空间可能就会被破坏。

4、智能指针shared_ptr类

4.1 创建shared_ptr

shared_ptr允许多个指针指向同一个对象,定义在memory头文件中。类似vector,智能指针也是模板,所以在创建智能指针时需要提供指针可以指向的类型。默认初始化的智能指针中保存着一个空指针。

shared_ptr<string> p1;
shared_ptr<int> p2;

最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr.

//指向一个值为52的int的shared_ptr
shared_ptr<int> p3 = make_shared<int>(52);
//指向一个值初始化的int,即0
shared_ptr<int> p4 = make_shared<int>();
//指向一个值为“999”的string
shared_ptr<string> p5 = make_shared<string>(3,'9');

下表是其支持的操作:
在这里插入图片描述

4.2 shared_ptr的拷贝和赋值

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象:

auto p = make_shared<int>(42);
auto q(p);//p和q指向相同的对象,此对象有两个引用者
auto r = make_shared<int>(42);//r指向的int只有一个引用者
r = q;//给r赋值
//递增q指向的对象的引用计数
//递减r原来指向的对象的引用计数
//r指向原来的对象已经没有引用者,会自动释放

每个shared_ptr都有一个关联的计数器,通常称为引用计数。一旦shared_ptr的引用计数为0,他就会通过析构函数自动销毁自己所管理的对象并释放关联的内存。
当我们拷贝、赋值或销毁一个类对象时,它的shared_ptr成员会被拷贝、赋值或销毁。

4.3 shared_ptr和new结合使用

shared_ptr<int> p1(new int(42));//p1指向一个值为42的int

接受指针参数的智能指针的构造函数是explicit的,所以不能将一个内置指针隐式的转换为一个智能指针,必须使用直接初始化形式。

shared_ptr<int> p1 = new int(1024);//错误,必须使用直接初始化
shared_ptr<int> p2(new int(1024));//正确,使用了直接初始化形式

同样,一个返回shared_ptr的函数不能在其返回语句中隐式转换一个普通指针。

shared_ptr<int> clone(int p)
{
	return new int(p);//错误,不能隐式转换
	//正确:显示的用int*创建shared_ptr
	return shared_ptr<int>(new int(p));
}

Tips:默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,默认使用delete释放它所关联的对象,如果要讲智能指针绑定到一个指向其他类型资源的指针上必须自己定义一个替代delete的操作。

不要混合使用指针指针和普通指针

void process(shared_ptr<int> ptr)}
{
	//使用ptr
}//ptr离开作用域,被销毁
shared_ptr<int> p(new int(52));//引用计数为1
process(p);//拷贝p会递增它的引用计数,在process中引用计数值为2
int i = *p;//正确:引用计数值为1
int* x(new int(1024));//危险,x是一个普通指针
process(x);//错误,不能隐式转换
process(shared_ptr<int>(x));//合法的,但内存会被释放
int j = *x;//未定义的:x是一个空悬指针

Tips:使用一个内置对象访问一个智能指针很危险,因为我们无法知道对象何时被销毁。

4.4 智能指针与异常

使用智能指针,即使程序块过早结束,也能确保在内存不再需要时将其释放。函数退出有两种情况,正常处理结束或者发生异常,无论哪种情况,局部对象都会被销毁

void f1()
{
	shared_ptr<int> sp1(new int(52));//分配一个新对象
	//这段代码抛出一个异常,且未被捕获
}//在函数结束是shared_ptr自动释放内存。

如果使用new和delete则不安全,如果在new和delete之间发生异常且未被捕获,则内存将不会被释放。

void f2()
{
	int *sp2 = new int(52);//动态分配一个新对象
	//这段代码抛出一个异常,且未被捕获。
	delete sp2; //退出前释放
}

5 智能指针使用注意事项

1)不使用相同的内置指针初始化(或reset)多个智能指针。
2)不delete get()返回的指针
3) 不使用get()初始化或者reset另一个智能指针
4) 如果使用了get()返回的指针,记住当最后一个对应智能指针销毁时,你的指针就变为无效了。
5) 如果你使用智能指针管理的资源不是new分配的内存,记住传递给他一个删除器。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值