动态内存
静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。栈内存或者静态内存中的对象由编译器自动创建和销毁。对于栈对象,仅在其定义的程序块运行时才存在;static对象在使用之前分配,在程序结束时销毁。除了静态内存和栈内存之外,程序还拥有一个称作
自由空间或者
堆的内存池。程序利用堆来存储动态分配的对象。动态对象的生存期由程序来控制,与其在何处创建无关,只有显式的释放时才会被销毁。
[#
知识点:全局对象在程序加载时进行分配,在程序结束时销毁,全局对象、静态对象(注意两者的区别,全局对象可以作用于多个文件而静态对象只能作用于本文件)保存在静态区,一般有系统自动释放;局部自动对象在程序进入其所在的定义区进行块时创建,在离开该作用区域时销毁,局部对象(函数内的非静态对象一般存储于栈区),一般由系统自动释放;动态对象有程序来分配,并由程序释放,一旦没有主动释放,这块内存将被认为泄露了。#]
使用动态内存的原因:
1) 程序不知道自己需要使用多少对象;
2) 程序不知道所需对象的准确类型;
3) 程序需要在多个对象之间共享数据。
new与delete
动态内存的管理是通过一对运算符来完成:new、delete。new在动态内存中为对象分配空间并返回一个指向该对象的指针;delete接受一个动态对象的指针销毁该对象并释放内存。
new
自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针。
默认情况下,动态分配的对象是默认初始化的,因此此时内置类型和符合类型的对象的值是未定义的(注意此时虽然称为默认初始化,但并没有对其进行初始化),而类类型对象将用默认构造函数来进行初始化。
可以使用auto(obj)来推断类型,但有一点值得注意的是,new之后接上auto构成的初始化器,会将obj对象的值作为新分配的对象的值。(
可否使用decltype?)
new可以创建const对象。
new也能返回void* 指针,当内存分配失败的时候。此种new操作称为
定位new。
// T *name = new T;
int *pi = new int; //pi指向一个动态分配,未初始化的无名对象。
string *ps = new string; //ps指向一个动态分配的空string
// T *name = new T(args);
int *pi = new int(0); //pi指向一个动态分配,初始为0的对象。
string *ps = new string(3, 'g'); //ps指向一个动态分配的初始化为"999"string
vector<int> *pv = new vector<int> {0, 1, 2}; //pv指向一个动态分配的初始化为{1,2 3}的vector
delete
为了防止动态内存耗尽,在动态内存使用完后应该进行释放以归还给系统。
delete接受一个指向动态分配对象的指针以释放对象。有的机器在对指针指向的动态内存进行delete之后,仍旧会继续保持被释放的内存的地址。
delete执行的是连个操作:销毁对象和释放内存。
new可以开辟内存空间,但对内置类型或者复合类型并不进行初始化。
释放一个并非new分配的内存或者将相同的指针释放多次,其行为是未定义的。
delete之后,指针就变成了空悬指针,即指向一块曾经保存数据对象但现在已经失效的内存的指针。空悬指针有着和未初始化的指针相类似的缺点。最好在释放内存之后,对指针重置,即将其变为空指针。可以使用nullptr来对空悬指针进行赋值。
当多个指针指向相同的内存时,delete一个指针就可以释放内存,使其他指针都变为空悬指针,但重置一个指针并不会作用于其他指针。
delete p;//p是一个指向动态对象的指针或者空指针
智能指针
当使用动态内存时:如果忘记释放内存,容易造成内存泄露;如果过早释放,容易造成野指针。为了更容易的使用内存,新标准提供了两种智能指针负责自动释放动态内存:shared_ptr、unique_ptr。除此之外还定义了一种弱指针:weak_ptr。三种类型均定义在memory头文件中。
shared_ptr类
1、定义
类似于vector,智能指针也是模版,创建智能指针时需提供额外的信息。
shared_ptr<string> p1;
2、初始化
3、操作
最安全的分配和使用动态内存的方式就是调用make_shared的标准函数,结合auto可以更方便地使用。
拷贝或者赋值时,shared_ptr会记录有多少个其他的shared_ptr指向相同的对象,这样的计数器称之为
引用计数。当shared_ptr被拷贝时,与其关联的引用计数就会递增;当shared_ptr被赋值或者销毁时,其引用计数会递减。(
这样的引用计数是如何通知给每一个指向相同的对象的智能指针的?)
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动通过
析构函数来销毁此对象。析构函数一般用来释放对象所分配的资源。
shared_ptr不直接支持管理动态数组,但可以同多自己定义删除器来实现。因为默认情况下,共享智能指针调用delet来销毁对象。
share_ptr为定义下标运算符,而且智能指针类型不支持指针算术运算。
shared_ptr<T> sp //指向类型为T的空智能指针
*p //解引用
p //将p作为一个判断条件,若p指向一个对象则返回true
p->mem //等价于(*p).mem
p.get() //返回p中保存的指针,
swap(p, q) //交换p和q中的对象
p.swap(q) //交换p和q中的对象
//shared_ptr特有的操作
make_shared<T>(args) //返回一个指向类型为T的对象的share_ptr,并用args初始化对象。
shared_ptr<T> p(q) //p是q的拷贝,会增加q中的计数器且q中的指针必须能转换为 T* 。
p = q //此操作会递减p的引用计数,递增q的引用计数。若p的计数为0,则释放内存。
p.unique() //若p.use_count() 的值为1,则返回true。
p.use_count() //返回与p共享对象的智能指针的数量。
//shared_ptr和new的结合使用
shared_ptr<T> p(q) //p管理内置指针q所指向的对象,q必须指向new分配的内存,且能够转换为T*类型
shared_ptr<T> p(u) //p从unique_ptr处接管了对象的所有权,将u置为空
shared_ptr<T> p(q, d) //p管理内置指针q所指向的对象,q必须指向new分配的内存,且能够转换为T*类型。p将使用可调用对象d来代替delete
shared_ptr<T> p(p2, d) //p是shared_ptr p2的拷贝,p将用可调用对象d来代替delete。
p.reset() //若p是唯一指向其对象的shared_ptr,reset会释放掉此对象。
p.reset(q) //若传递了可选的参数内置指针q,则会令p指向q,否则会置p为空。
p.reset(q, d) //若传递了参数q和d,则会调用d来释放对象。
unique_ptr类
与shared_ptr不同的是,
每个时刻只能有一个unique_ptr指向一个给定的对象。当unique_ptr被销毁时,对象也被销毁。
由于unique_ptr拥有所指定的对象,因此不能执行拷贝和 赋值操作。但有一个例外,就是在该指针即将被销毁时可以进行拷贝和赋值操作,例如返回一个unique_ptr。
可以使用相应的release和reset方法将其对资源的拥有权转移给另一个。release函数会切断智能指针对内存的拥有权但并不会释放内存,一旦该内存没有被其他指针接管,那么该内存就相当于被泄露了。
[
##当指针的类型是数组时,标准库提供了可以管理new分配数组的unique_ptr版本,使用release函数会自动释放动态数组对象。但此时的指针并不能执行点和箭头运算符,却可以使用下表运算符来对数组中的元素进行访问。
##]
没有与make_shared类似的库函数来返回一个unique_ptr,所以定义一个unique_ptr时,需要将其与new创建的指针进行绑定。类似于shared_ptr,初始化时必须使用直接初始化。
unique_ptr<T> up //指向类型为T的空智能指针
*p //解引用
p //将p作为一个判断条件,若p指向一个对象则返回true
p->mem //等价于(*p).mem
p.get() //返回p中保存的指针,
swap(p, q) //交换p和q中的对象
p.swap(q) //交换p和q中的对象
//unique_ptr特有的操作
unique_ptr<T> u1 //空指针
unique_ptr<T, D> u2 //u2使用类型为D的对象来释放它的指针
unique_ptr<T, D> u(d) //用类型为D的对象d来代替delete
u = nullptr //释放u指向的对象
u.release() //u放弃对指针的控制权,返回指针,并将u置为空
u.reset() //释放u指向的对象
u.reset(q) //令u指向内置指针q的对象
u.reset(nullptr)
weak_ptr类
weak_ptr类是一种不控制所指向对象生存期的智能指针,
指向由一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变其引用计数。
当定义一个weak_ptr时,要用一个shared_ptr来初始化它。(
是否可以使用弱指针来对内存进行释放?)
因为weak_ptr指向的对象可能被shared_ptr释放掉了,因此很可能用该弱指针访问对象时发生错误。
weak_ptr<T> wp //空指针
weak_ptr<T> wp(sp) //与sp指向相同对象的weak_ptr
w=p //p为shared_ptr或者weak_ptr
w.reset() //将w置空
w.use_count() //与w共享对象的shared_ptr的数量
w.expired() //若w.use_count()为0,返回true
w.lock() //如果expired为true,返回一个空的shared_ptr,否则返回一个指向w的对象的shared_ptr
动态数组
new和delete运算符一次分配和释放一个对象。当需要一次为很对象分配内存时,C++提供了两中一次分配一个对象数组的方法:新的new表达式语法、allocator类。