管理动态内存的方法
我们所编写的程序中所使用的对象都有着严格定义的生存期。全局对象在程序启动时分配,在程序结束时销毁;对于局部自动对象,当我们进入其定义所在的程序是被创建,在离开时被销毁。局部static对象在第一次使用前分配,在程序结束时销毁。
在编写程序时,除了要管理自动和static对象外,还需要管理动态内存,而动态内存的生存期与它们在哪里创建无关,只有当显式地被释放时,才会被销毁,而往往在写程序时,我们很容易忘记显式的释放动态内存,这样很容易造成内存泄露,造成程序崩溃,而这些错误往往编译器是发现不了的,不会提醒程序员,所以对动态内存的管理非常重要。
这里,介绍两种常见的动态内存管理方法。
1.使用new和delete对来管理;
2.使用智能指针来管理,其中使用shared_ptr 和make_shared。
第一种:使用new和delete对来管理
这是常见的一种管理方法,也是我们最常用的一种方法,通常new和delete是成对的出现,当用new为一个对象申请了内存后,必须在使用完后,使用delete来销毁内存。
例:int *p = new int;//p指向一个动态内存的,未初始化的无名对象
此时,new表达式在自由空间构造一个int对象,并返回指向该对象的指针。在使用完p之后,后面不在使用的时候一定要技能用delete来销毁内存,
delete p;
这里使用delete后,程序会销毁申请的内存,但是此时指针p还是指向那个内存,只是内存此时无用,这里为了避免后面又使用到该指针,我们还需要在后面将p赋值为空指针。
p = nullptr;
第二种:使用智能指针来管理动态内存
这里主要是使用shared_ptr和make_shared来管理。使用智能指针可以避免程序员忘记销毁内存而造成的内存泄露,从而导致的系统崩溃。因为它当没有指针指向该内存的时候会自动释放内存。
例:shared_ptr<int> *p = make_shared<int>();这里也是p指向一个动态内存,其值初始化为0
这里我们建议使用智能指针来管理动态内存。具体优势,我们通过下面的例子来解释:
int *q = new int(42), *r = new int(100); r = q; auto *q2 = make_shared<int>(42), r2 = make_shared<int>(100); r2 = q2;
下面我们解释这段程序。
对于普通指针部分,首先分配了两个int型对象,指针分别保存在p和r中。接下来,将指针q的值赋给了指针r,这样带来了两个非常严重的内存管理问题。
1.首先是一个直接的内存泄露问题,r和q一样都指向42的内存地址,而r中原来保存的地址--100的内存再无指针管理,变成“孤儿内存”,从而造成内存泄露。
2.其次是一个“空悬指针”问题。由于r和q指向同一个动态对象,如果程序编写不当,很容易产生释放了其中一个指针,而继续使用另一个指针的问题。继续使用另一个指针指向的是一块已经释放的内存,是一个空悬指针,继续读写它指向的内存可能导致程序崩溃甚至系统崩溃的严重问题。
而shared_ptr则可以很好的解决这个问题。首先,分配了两个共享的对象,分别由指针p2和r2指向,因此它们的引用次数都为1.接下来,将q2赋予r2.赋值操作会将q2指向的地址赋予r2,并将r2原来指向的对象的引用次数减1,将q2指向的对象的引用次数加1.这样,前者的引用次数变为0,其占用的内存空间会被释放,不会造成内存泄露。而后者的引用计数变为2,也不会因为r2和q2之一的销毁而释放它的内存空间,因此也不会造成空悬指针的问题。
从上面的例子可以看出,使用智能指针比使用new和delete更方便,更实用,可以避免我们编写代码时的很多内存管理问题。