欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

智能指针(一)

程序员文章站 2024-03-14 18:28:04
...

内存使用

一般情况下有三种内存,静态内存用于保存局部static对象、类static数据成员以及函数外被定义的变量,这种对象或者变量在使用之前被分配,程序结束时销毁;栈内存用于保存在函数内的非static对象,栈对象只在程序块运行时存在,程序流离开时销毁;以及*空间,也叫堆,用来保存动态分配的对象,即运行时才被分配的对象,必须显示销毁。

动态内存的管理

动态内存的管理可以使用new和delete来完成,前者创建,后者销毁。如果不及时进行销毁,会造成内存泄漏。
或者使用智能指针来完成,包含shared_ptr和unique_ptr两种,前者允许多个指针指向同一个对象(每个shared_ptr都有一个引用计数来计算指向对象的指针个数),而后者则只能由一个指针指向一个对象。同时标准库还定义了一个伴随类weak_ptr,指向shared_ptr所管理的对象。

new和delete

new能在堆内创建一个匿名对象(该对象可以进行直接,值和列表初始化),然后返回指向该对象的指针。
new也可以配合auto来使用,自行推断对象的类型,但是初始化的参数只能有一个。

auto a=new auto(b); //true
auto a=new auto(b,c); //false

一般情况下,new如果分配内存失败会抛出异常(bad_alloc类型),如果不想抛出异常,则如下:

int *a=new int;  //分配失败,抛出异常
int *a=new (nothrow)int;//分配失败,不抛出异常,new返回空指针

new申请的指针如果不delete就会一直存在,并且delete只对当前的指针有效,即如果存在其他指针也指向了被释放的地址时,程序同样会出错,甚至可能之后又delete了已经被释放的对象,这些都是非法的操作。所以及时delete是很有必要的。

shared_ptr

智能指针也是模板的一种,所以创建时需要指出类型,,也支持和指针一样的解引用操作。
对于shared_ptr支持的常见操作而言,有以下几种:

make_shared<T>(args) 	//初始化
shared_ptr<T>p(q)  		//q为智能指针,则拷贝指针;q指向new分配的对象,则进行接管;如果q为unique_ptr,则接过所有权,将q置空
shared_ptr<T> p   		//空shared_ptr,使用delete释放
shared_ptr<T> p(q,d)	//q为智能指针,则p拷贝指针;q指向new分配的对象,则进行接管;均使用可调用对象d代替delete
p=q						//赋值
p.unique()  			//如果p.use_count()为1,则为true,判断引用者是否唯一
p.use_count()  		 	//返回和p共享的智能指针数量,主要用于调试
p.get()					//返回p中保存的指针(内置)
p.swap(q)				//交换指针
swap(p,q)
p.reset()				//当p为唯一shared_ptr,将释放此对象;
p.reset(q)				//当p为唯一shared_ptr,将释放此对象;当q为内置指针时,会将p指向q
p.reset(q,d)			//当p为唯一shared_ptr,将释放此对象;当q为内置指针时,d为函数,会将p指向q,调用函数d来释放

其中比较有意思的是赋值操作,它会将后者指向的对象的引用计数递增,将前者指向的对象的引用技术递减,如果减少为0,就会自动释放所管理的对象,具体如下:

auto a=make_shared<int>(1),b=make_shared<int>(2);
a=b; //给a赋值,a会指向b所指向的地址
	 //a不再指向原来的对象1,则a原来指向的对象的引用计数递减,此处为0,则a原来指向的对象没有其他的引用者,自动释放对象1
	 //b指向的对象现在多了一个引用者a,故此对象的引用计数递增,此处为2

shared_ptr最大的好处就是不用手动delete,只要一个对象不再被任何一个shared_ptr所指向,它就会自动销毁。

new和shared_ptr结合

同时,shared_ptr可以和new结合使用,可以将new返回的指针来创建智能指针,但是这种创建必须是显式的(explicit),也就是说,将new返回的指针隐式转换为shared_ptr是非法操作,示例如下:

shared_ptr<string> p1 = new string("a"); //false
shared_ptr<string> p1(new string("a"));  //true

如果用shared_ptr绑定到new的指针,那这块内存的管理就会被接管,不使用时就会被销毁,但是如果再使用这个new的指针来访问shared_ptr的对象,就可能出错,因为对象何时被销毁时未知的。
所以使用make_shared创建比较好,可以避免这种问题。

p.get()操作

p.get()的设计目的是当需要向不能用智能指针的地方传递一个内置指针时使用
p.get()的作用是返回一个内置指针,指向智能指针指向的对象,并且返回的指针不能被delete
有几个注意点:

  1. 返回的指针不能被delete,因为如果delete就会销毁对象,那么之后智能指针就无法释放已经被释放的对象
auto p=make_shared<int>(1);
auto a=p.get()
delete a;
  1. 返回的指针不能用于初始化另一个智能指针,这样会会有两个独立的智能指针指向同一个对象,并且引用计数都是1,这种行为是未定义的。这样只要其中一个不再使用,所管理的内存就会被释放,另一个指针就变成空悬指针,使用就是非法的,同时会发生二次释放。如下示例:
shared_ptr<string> p(new string("a"));
string* q = p.get();
shared_ptr<string> pp(q); //p和pp引用计数均为1

p.reset()操作

当有多个shared_ptr共享同一个对象时,就可以用reset来获得新的拷贝,并且和原来的对象区分开来,如下所示:

auto p = make_shared<int>(100);
auto q(p);
if(!p.unique())				//如果当前的引用者不是唯一
	p.reset(new int(*p));	//就创建新的拷贝,此时p会重新指向一个new的指针,引用计数为1
*p += 100;
cout << *p << endl;

unique_ptr

unique_ptr独占所指向的对象,即引用计数为1,和shared_ptr不同的是,**它只能绑定到一个new指针上面(直接初始化),并且不支持拷贝和赋值操作。**虽然不支持拷贝和赋值操作,但是可以拷贝和赋值一个将要被销毁的unique_ptr,比如作为函数的返回值或者返回一个局部对象时,这是一种特殊的拷贝(移动操作)。
unique_ptr支持的操作:

unique_ptr<T> p(args)	//初始化
unique_ptr<T> p   		//空unique_ptr,使用delete释放
unique_ptr<T,D> p	 	//空unique_ptr,使用类型D的可调用对象释放
unique_ptr<T,D> p(d)	//空unique_ptr,使用类型D的可调用对象d释放
p.get()					//返回p中保存的指针(内置)
p.swap(q)				//交换指针
swap(p,q)
p=nullptr				//释放对象
p.release()				//放弃指针控制权,返回指针,将p置空
p.reset()				//释放p所指向的对象
p.reset(q)				//释放p所指向的对象,如果q为内置指针,则让p指向该对象
p.reset(nullptr)		//释放p所指向的对象,将p置空

由于不能进行拷贝和赋值,所以指针的管理权之间的转移就要使用release或者reset来完成,比如:

unique_ptr<int>sp1(new int(10));
unique_ptr<int>sp2(sp1.release());	//release将sp1置空,指针10的指针,sp2用该指针初始化,获得所有权
unique_ptr<int>sp3(new int(100));
sp2.reset(sp3.release());			//reset释放了sp2所指对象,获得了sp3对象的所有权

weak_ptr

weak_ptr指向由shared_ptr所管理的对象,但是不控制对象的生存期,不改变引用计数。
weak_ptr支持的操作:

weak_ptr<T>w  		//空指针
weak_ptr<T>w(p)		//p为shared_ptr,将w指向p所管理的对象
w=p					//p可以为shared_ptr或者weak_ptr,赋值操作,共享对象
p.get()				//返回p中保存的指针(内置)
p.swap(q)			//交换指针
swap(p,q)
w.reset()			//置空
w.use_count()		//计算和w共享的shared_ptr的数量
w.expired()			//如果w.use_count()为0,则true,
w.lock()			//如果w.expired()为true,返回一个空shared_ptr;否则返回一个指向w对象的shared_ptr

weak_ptr需要用shared_ptr进行初始化,但是不能用make_shard,比如:

auto a = make_shared<int>(0);
weak_ptr<int>b(a);

同时,weak_ptr不能解引用,需要用lock()来获得指向对象的shared_ptr(如果非空),然会再访问。