c++动态内存与智能指针

目前为止我们学过静态内存和栈内存,分配在其中的对象由编译器自动创建和销毁,

静态内存:用来保存局部static对象、类的static数据成员、以及定义在任何函数体之外的变量。在对象使用之前分配,程序结束时销毁。

栈内存:用来保存定义在函数内的非static对象。仅在对象定义的程序块运行时存在,程序块运行结束时销毁。

除此之外,每个程序还拥有一个内存池,这部分内存被称为自由空间程序用堆来存储动态分配的对象——程序运行时分配的对象。动态对象不再使用时,需要显式的销毁它们。程序使用动态内存出于以下三种原因之一:

  • 程序不知道自己需要使用多少对象(比如容器类)。
  • 程序不知道所需对象的准确类型。
  • 程序需要在对各对象间共享数据。

1、new和delete

c++中动态内存的管理是通过一对运算符new和delete完成的:new为对象分配内存并返回指向该对象类型的指针,delete接受一个动态对象的指针,销毁指向的对象并释放其内存。

1.1、new

 (1)动态分配内存与初始化

  • 默认初始化

默认情况下,动态内存分配的对象是默认初始化的,意味着内置内心和组合类型(如指针和数组)的对象的值将是为定义的,类类型的对象将使用默认构造函数初始化:

int *p=new int;//pi指向一个动态分配的、无名的、未初始化的对象

  • 直接初始化

使用圆括号或花括号来初始化一个动态分配的对象:

int *p=new int(1024);

vector<int> *pv=new vector<int>{0,1,2,3};

  • 值初始化

在类名后面跟一对空括号来动态分配的对象进行值初始化:

int *p=new int();//值初始化为0

string *ps=new string();//值初始化为空字符串

对于定义了构造函数的类来说,要求值初始化是没有意义的,不管采用什么方式,对象都会通过默认构造函数来初始化。对于内置类型来说,值初始的对象由良好定义的值,而默认初始化的值是未定义的。

(2)动态分配const对象

const int *pc=new const int(1024);

1.2、delete

(1)释放内存

我们传递给delete的指针必须是指向动态分配的内存,或是一个空指针。释放一块并非new分配的内存,或者将相同的指针释放多次,其行为是未定义的:

int i,*pi=&i,*p2=nullptr;

double *pd=new double(3.14),*pd2=pd;

delete pi;//错误,pi指向的是局部变量,不是动态分配的内存

delete p2;//正确

delete pd;//正确

delete pd2;//错误,pd2指向的内存已经被释放掉了

(2)释放const对象

虽然const对象的值不能被改变,但是可以被销毁:

delete pc;

(3)delete之后重置指针

delete指针之后,指针值就变为无效的了,虽然指向的内存被释放了,但指针还在,很多机器上指针仍然保存着原来的动态内存的地址。delete之后的指针被称为空悬指针。为了消除指针与动态内存的关联,可以在delete之后将nullptr赋予指针,但这种方法只对这个指针有效,对多个指针指向相同内存的情况下还是作用有限:
int *p=new int(1024);

int *q=p;//p和q指向同一块动态内存

delete p;//p不再绑定任何对象,但是q变成了空悬指针

2、内存泄漏

内存泄漏:动态分配的内存忘记释放产生内存泄漏。

非法内存:尚有指针引用了内存的情况下释放了了它,产生引用非法内存的指针。

(1)函数调用返回动态内存指针

在调用返回指向动态内存的指针时,调用者需要记得释放内存:

int * fun(){

  return new int(1024);//调用者负责释放此内存

}

(2)函数体内申请动态内存

在函数体内申请的动态内存要注意释放:

void fun(){
  int *p=new int(1024);

}

p是指向这块内存的唯一指针,一旦函数返回,程序就没有办法释放这块内存了。

3、智能指针

为了更容易地使用动态内存,新的标准库提供了两种智能指针shared_ptr和unique_ptr来管理动态对象,可以自动释放对象内存,定义在memory头文件中。

3.1、shared_ptr

(1)创建指针

类似vector,智能指针也是模板,必须提供指针可以指向的类型<>:

shared_ptr<string>ps;

shared_ptr<vector<int>> pv;

默认初始化的智能指针中保存的是一个空指针。一般指针默认初始化是未定义的。

(2)分配内存——make_shared函数

make_shared函数在动态内存中分配一个对象并初始化它,返回指向对象的shared_ptr,同样需要指定想要创建对象的类型<>:
shared_ptr<int>pi=make_shared<int>();//如果不传递任何参数,对象执行值初始化

shared_ptr<int> pi=make_shared<int>(1034);//直接初始化

通常使用auto来保存make_shared的结果,这种方式更为简单:

auto p=make_shared<vector<string>>();

(3)引用计数

每个shared_ptr都有一个关联的计数器来记录有多少个shared_ptr指向相同的对象,通常称其为引用计数。

无论何时我们拷贝一个shared_ptr,计数器都会递增:

  • 当用一个shared_ptr初始化另一个shared_ptr
  • 将shared_ptr作为参数传递给一个函数
  • 将shared_ptr作为函数的返回值

计数器递减:

  • 给shared_ptr赋一个新值
  • shared_ptr被销毁(比如离开作用域)

(4)自动销毁对象

当对象的最后一个shared_ptr被销毁时,它会自动销毁指向的对象,它是通过析构函数完成 的,析构函数会递减它所指向对象的引用计数,当引用计数变为0时,析构函数会销毁对象,并释放它占用的内存。同理,当动态对象不再被使用时,shared_ptr会自动释放动态对象。

 

posted @ 2018-02-20 23:25  StormWendy  阅读(433)  评论(0编辑  收藏  举报