在 C++ 中使用 new
操作来分配和初始化动态对象,并返回一个指向对象的指针。delete
操作符则以此指针为操作数,销毁其指向的对象,并释放其内存。new
与delete
的使用不恰当是很容易造成内存泄漏或者非法内存的访问,比如下面的这段代码
1 | void Foo( ) |
第一眼感觉这段代码似乎没什么问题,内存资源也通过delete
操作释放了。但是没有人可以保证,在new
跟delete
之间的代码在执行时不会出现异常提前return
而造成没有机会执行到delete
操作。
为了使得动态内存易于使用并且更加安全,新标准中提供了两个智能指针来管理动态对象。智能指针的本质是一个RAII
类模型,将资源的申请与释放和栈对象的生命周期绑定在一起,当栈对象被销毁的同时会自动释放分配的内存。shared_ptr
允许多个指针指向同一个对象,unique_ptr
则拥有其指向的对象,因而是排外的。标准库还定义了weak_ptr
表示对shared_ptr
管理的对象的弱引用。所有这三个类都定义在memory
头文件中。
shared_ptr和unique_ptr共有的操作
shared_ptr<T> sp
unique_ptr<T> up
指向T
类型的对象的空指针;p
将p
用于条件中,如果指向一个对象将返回true;*p
解引用p
得到其指向的对象,如果p
是空的,结果未定义;p->mem
等同于(*p).mem
;p.get()
返回p
对象中保存的对象指针。返回指针所指向的对象可能会被智能指针删除。swap(p,q);
p.swap(q)
交换p
和q
中的指针
shared_ptr类
shared_ptr特有的操作
make_shared<T>(args);
返回一个T类型对象的智能指针,使用args
初始化对象;shared_ptr<T>p(q);
p
是shared_ptr
q
对象的拷贝,将增加q
的引用计数,q
中的指针必需可以转换为T*
;p
=q
;p
和q
是指向可转换的指针的智能指针shared_ptr
;减少p
的引用计数,增加q
的引用计数;如果p
的引用计数为0,删除其指向的对象的内存;p.unique()
; 如果p
的引用计数是1,则返回true,否则返回false;p.use_count()
; 返回p
所指对象的引用计数,这是一个很慢的的操作,主要用于调试
shared_ptr
是一个RAII
的模板类,其定义方式如下
1 | shared_ptr<int> p1; |
其它的定义和改变shared_ptr
的方式:
shared_ptr<T> p(new T());
将智能指针初始化为从new
返回的指针,shared_ptr
将接管这块动态内存shared_ptr<T> p(q);
p
将管理由内置指针q
所指向的对象,q
必须指向由new
分配的的内存,并且可以转为;shared_ptr<T> p(u);
p
接管unique_ptr
u
对象的所有权,并使得u
为空指针p.reset()
p.reset(q);
如果p
是指向对象的唯一指针,reset
将会释放p
所指的对象。如多提供了额外的内置指针q
,将在之后使得p
指向q
,否则将使p
变为空指针
make_shared函数
在使用shared_ptr
模板类后最安全分配和使用动态内存的方法就是使用库函数make_shared()
。该函数在分配内存并初始化对象后会返回指向该对象的shared_ptr
智能指针。make_shared
被定义在头文件<memory>
,使用方法如下
1 | shared_ptr<int> p1 = make_shared<int>(1); |
shared_ptr如何适当释放动态内存
每个shared_ptr
对象都会有一个引用计数器。每当初始化另一个shared_ptr
或者在赋值表达式的右边,或作为参数传递给函数,或从函数中作为返回值返回都会让引用计数加一。当给shared_ptr
赋予新值时,或者shared_ptr
对象本身被销毁时,引用计数都会减一。当引用计数变为0时,shared_ptr
的析构函数将销毁其指向的对象,并释放内存。引用计数器的作用就是让shared_ptr
可以在适当的时机释放其指向的对象的内存。
unique_ptr
unique_ptr
具有其指向对象的所有权,只有一个unique_ptr
指向一个对象,其独占所指向的对象;对象将在unique_ptr
销毁时被释放。
unique_ptr特有的操作:
unique_ptr<T> u1
unique_ptr<T, D>u2
定义两个unique_ptr
空指针,它们可以指向类型为T
的对象。u1
使用delete
来释放指针,u2
类型使用类型为D
的可调用对象delete
进行对象释放。unique_ptr<T, D> u(d)
定义unique_ptr
空指针,使用类型为D
的可调用对象d
进行对象释放;u = nullptr;
删除u
所指向的对象,并使其为空指针;u.release();
交出u所指对象的控制权,返回p
所指对象的内置指针,并使得u
为空指针;u.reset();
删除u
所指的对象;u.reset(q)
u.reset(nullptr)
删除u
所指向的对象,并使得u
指向内置指针指向的对象,否则u
为空指针
weak_ptr
weak_ptr
是一个不控制其指向对象的生命周期的智能指针,相反它指向shared_ptr
管理的对象。将weak_ptr
绑定到shared_ptr
并不会改变引用计数。当最后一个shared_ptr
被销毁时,其管理的对象依然会被释放,即使weak_ptr
还指向该对象。因此,weak_ptr
被称为弱指针。
weak_ptr<T> w
创建weak_ptr
的空指针,其指向T
类型对象;weak_ptr<T> w (sp)
创建指向与shared_ptr
sp
所指向相同对象的weak_ptr
,T
必须与sp
所指向的对象类型可以相互转换;w
=p
p
可以是shared_ptr
或者weak_ptr
,赋值后w
指向与p
相同的对象;w.reset()
使得w
为空指针;w.use_count()
返回指向同一个对象shared_ptr
的个数;w.expired()
如果没有shared_ptr
指向对象时返回true,否则返回false;w.lock()
如果已经过期,则返回一个空的shared_ptr
,否则返回指向该对象的shared_ptr
;
使用weak_ptr
,可以在不影响对象的生命周期的情况下,安全地访问该对象。
智能指针使用的约定
- 不要使用相同的内置指针去初始化超过一个智能指针;
- 不要使用删除
get
函数返回的指针; - 不要用
get
函数返回指针去初始化或reset
别的智能指针; - 当使用
get
函数返回的指针时,应当记住当最后一个智能指针销毁时,这个指针会变得无效; - 使用智能指针管理资源而不是内存时,记得传递一个
删除器(deleter)
过去;