12.动态内存

C++

Posted by HustDsy on October 8, 2020

12.动态内存

主要从动态内存与智能指针,动态数组这两个方面来进行学习

12.1 动态内存与智能指针

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

然而动态内存的管理方式很容易造成内存泄漏等问题,因为确保正确的时间释放内存是极其困难的,为了更加安全和容易使用内存,新的标准库提供了两种智能指针.称作为shared_ptr,允许多个指针指向同一个对象,以及unique_ptr,”独占”所指向的对象。标准库还定义了一个weak_ptr的伴随类,指向shared_ptr所管理的对象,头文件均为memory.

12.1.1 shared_ptr

操作如下表:我们可以认为每一个shared_ptr都有一个计数器,我们称作为引用计数

image-20201008123933152

程序使用动态内存的三种情况:

  • 程序不知道自己需要使用多少对象-vector
  • 程序不知道所需对象的准确类型
  • 程序需要在多个对象之间共享数据

对于第三种情况,我们使用过的类中分配的资源和对应对象的生命期是一致的,首先看vector,每个容器由他自己的元素,当我们拷贝他的时候,原vector和副本vector中的元素是相互分离的

vector<int>v1;
{
  vector<int>v2={1,2,3};
  v1=v2;
}//离开作用域v2被销毁了,v1保存着v2三个元素的拷贝

由一个 vector分配的元素只有当这个vector存在时オ存在。当一个vector被销毁 时,这个vector中的元素也都被销毁。但某些类分配的资源具有与原对象相独立的生存期。例如,假定我们希望定义一个名为Blob的类,保存一组元素。与容器不同,我们希望Blob对象的不同拷贝之间共享相 同的元素。即,当我们拷贝一个Blob时,原Blob对象及其拷贝应该引用相同的底层元素。一般而言,如果两个对象共享底层的数据,当某个对象被销毁时,我们不能单方面地 销毀底层数据。

Blob<string>v1;
{
  Blob<string>v2={1,2,3};
  v1=v2;//v2和v1享有一样的元素
}//离开作用域v2被销毁了,但v2d元素没有被销毁
//v1指向原来的数据

这里的话就需要用到动态内存了,这里的存储数据的容器为shared ptr<std: vector<std: string» data

12.1.2直接内存管理

*new:分配内存

delete:释放分配的内存

int*p1=new int(1);
int*p1=new int;//默认初始化,*p1的值未定义
int*p1=newint();//默认为0
string*p1=new string;//默认初始化为空的string
string*p1=new string();//默认初始化为空的string 

一般delete只是释放了内存,但是在很多机器上仍然保存着动态内存(已经释放了的)的地址,delete之后,指针就变为了空悬指针,也就是指向一块曾经保存数据对象但现在已经无效的内存的指针。

12.1.3shared_ptr和new的使用

image-20201008130739733

image-20201008130749763

可以使用new来初始化shared_ptr但是无法将一个内置指针隐式转换为一个智能指针。

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

不要混合使用智能指针与普通指针,因为这样子内存管理方式会为智能指针的形式,智能指针可能释放了普通指针。

void process(shared_ptr<int>p){
}//离开作用域,p就会被释放
//使用上一个函数是传入一个shared_ptr<int>p
shared_ptr<int>p(new int(1));//引用计数为1
process(p);//引用计数为2,因为拷贝了一份p
int q=*p;//正确 引用计数为一
//下面是一个错误的例子
int*p(new int(1));
process(shared_ptr<int>(p);
int a=*p;//未定义,p是一个空悬指针

12.1.4unique_ptr

它不支持拷贝和赋值操作,而且只能使用直接初始化形式

unique_ptr<int>p=new int(1);
unique_ptr<int>p2=p;//错误 不支持赋值
unique_ptr<int>p2(p);//不支持拷贝

image-20201008131740472

12.1.5weak_ptr

image-20201008131942911

12.2动态数组

大多数应该使用标准库定义的容器,而不是动态数组,因为容器更不容易出现内存管理错误,并且可能有更好的性能。

12.2.1 new和数组

int*a=new int[10]();//10个初始化值为0的int
int*a=new int[10];//10个未初始化的int
//新标准中,支持花括号
int*b=new int[2]{1,2};

//动态分配一个空数组是合法的
char a[0];//非法
char*a[0];//合法,但是不能解引用

//释放动态数组或者为空
delete []a;
//释放动态分配的对象或者为空
int *b-=new int();
delete b;

//指向动态数组的unique_ptr
unique_ptr<int[]>up(new int[10]);
up.release();//自动调用delete销毁指针
//可以通过up[i]进行访问
up[i]=10;

//使用shared_ptr的时候需要自己设置删除指针
sahred_ptr<int>sp(new int[10],[](int*p){delete []p});
//需要通过get函数将其转换为内置指针
*sp.get()=1;

12.2.2 allocator类

image-20201008150008679

allocator<string>a;
auto p=a.allocate(10);//分配10个未构造的内存
a.deallocate(p,n);//从p开始的n个类型进行删除
a.construct(p,"10");//假设p是第一个位置,现在第一个位置有个构造的数据
*p;//正确 为10
*(p+1)//灾难,还没有构造
a.destroy(p);//将p处的构造出来的数据销毁

image-20201008150556843

为创建的一块动态内存分配数据