常用的智能指针

  

使用指针有时候是比较危险的,可能存在空指针,野指针问题,并造成内存泄漏的问题。可是指针又非常的高效,所以我们希望以更安全的方式来使用指针:

  1. 使用更安全的指针——智能指针
  2. 不使用指针,使用安全的方式——引用

C++中推出了四种常用的智能指针:unique_ptr、shared_ptr、weak_ptr和C++11中已经废弃的auto_ptr,auto_ptr在C++17中被正式删除。

要记住,智能指针和指针一样都是栈中的变量,它们指向堆区中的内容。

auto_ptr

在这里插入图片描述
由new expression获得对象,它假设我们的对象是在栈空间中创建,所以在auto_ptr对象销毁时(auto_ptr跳出作用域),他所管理的对象也会被自动delete掉。

所有权转移:当我们用auto_ptr指向一个对象,而不小心用另一个auto_ptr指向同一个对象,那么这个时候原来的auto_ptr就会失效,不再拥有这个对象,而是指向nullptr。这是在拷贝/赋值的过程中把指针对原对象的内存控制权剥夺的。所以这让我们很难管理程序,会造成混乱。

#include<string>
#include<iostream>
#include<memory>   //智能指针头文件
using namespace std;
int main()
{
  {		//使用大括号是为了确定auto_ptr的范围,出了大括号,auto_ptr将失效
	//对基本类型int使用auto_ptr
	auto_ptr<int> pl(new int(10));
	cout<<*pl<<endl;


	//对string数组使用auto_ptr
	auto_ptr<string> language[5] = {		
		auto_ptr<string>(new string("C")),
		auto_ptr<string>(new string("Java")),
		auto_ptr<string>(new string("C++")),
		auto_ptr<string>(new string("Python")),
		auto_ptr<string>(new string("Rust")),
	};
	auto_ptr<string> pC;
	pC = language[2];//这个时候language[2]所有权转移,language[2]不再引用该字符串而是变成空指针nullptr
	}		//出了作用域,智能指针被析构
	return 0;
}

unique_ptr

auto_ptr是为了解决我们在堆空间中创建对象忘记释放的问题,它可以自动释放对象。但是问题在于auto_ptr与对象之间耦合性过于强壮,auto_ptr被销毁时它指向的对象也会被销毁,而且存在所有权转移的问题。所以我们基本不再使用了。

在这里插入图片描述

unique_ptr是专属所有权,所以unique_ptr管理的内存,只能被一个对象持有,不支持赋值和复制。

移动语义:unique_ptr禁止了拷贝语义,但是有时候我们也会需要能够转移所有权,于是提供了移动语义,即可以使用std::move()进行控制所有权地方转移。

#include<string>
#include<iostream>
#include<memory>   //智能指针头文件
using namespace std;
int main()
{
	auto w = std::make_unique<int>(10);  //使用make_unique方法获取unique_ptr
	cout<< *(w.get()) <<endl;			//get方法返回一个对象

	//auto w2 = w; //编译错误,unique_ptr不支持赋值语句

	//unqiue_ptr只支持移动语义
	auto w2 = std::move(w);  //w2获得内存所有权,此时w等于Nullptr
	cout << (w.get() != nullptr) ? (*w.get() : -1) <<endl;  //输出-1
	cout << (w2.get() != nullptr) ? (*w2.get() : -1) <<endl;  //输出10
	return 0;
}

shared_ptr和weak_ptr

auto_ptr和unique_ptr有一个缺陷,同一时刻一个对象只能有一个智能指针指向它,这就带来了局限性。我们需要让对象可以共享访问。

在这里插入图片描述
shared_ptr通过一个引用计数共享一个对象。它是为了解决auto_ptr在对象所有权上的局限性。在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销。

当引用计数为0 时,该对象没有被使用,可以进行析构。

引用计数也会带来一些副作用,比如循环引用的问题,导致堆内的内存无法正常回收,造成内存泄漏。如下图,有A和B两个对象,各含有一个shared_ptr pA和pB。某一时刻pA指向的对象是pB,而pB指向的对象是pA。当A对象想要释放自己的时候,由于释放自己要释放自己内部的所有内容,因为内部智能指针指向B,那么就需要释放B,而B要释放就要释放pB指向的A对象,这样就形成了一个环结构,相互指向无法释放。
在这里插入图片描述
为了避免循环引用,weak_ptr被设计与shared_ptr共同工作,用一种观察者模式工作。它协助shared_ptr工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着weak_ptr只对shared_ptr进行引用,而不改变其引用计数,当被观察的shared_ptr失效后,相应的weak_ptr也失效。

由下图可知,A中有一个shared_ptr指向B,当B对象也需要指针指向A的时候,我们不需要声明shared_ptr,而是声明一个weak_ptr,它不保有引用计数。但是shared_ptr失效后,这个weak_ptr也失效。
在这里插入图片描述

#include<iostream>
#include<memory>
using namespace std;
int main()
{
  {
	//shared_ptr代表的是共享所有权,即多个shared_ptr可以共享同一块内存
	auto wA = shared_ptr<int>(new int(20));
	{
		auto wA2 = wA;
		cout<<((wA2.get() != nullptr) ? (*wA2.get()) : -1) <<endl;  //20
		cout<<((wA.get() != nullptr) ? (*wA.get()) : -1) <<endl;	//20
		cout<<wA2.use_count()<<endl;					//引用计数,2
		cout<<wA.use_count()<<endl;						//2
	}
	cout<<wA.use_count()<<endl;						//跳出作用域,wA2消亡,此时引用计数为1

  }
  //跳出作用域,引用计数为0,销毁指针


	//move语法
	auto wAA = std::make_shared<int>(30);
	auto wAA2 = std::move(wAA);    //此时wAA等于nullptr,wAA2.use_count()=1


	return 0;
}
#include<string>
#include<iostream>
#include<memory>
using namespace std;

struct B;
struct A{
	shared_ptr<B> pb;
	~A()
	{
		cout<<"~A()"<<endl;
	}
};

struct B{
	shared_ptr<A> pa;
	~B()
	{
		cout<<"~B()"<<endl;
	}
};

//以上pa和pb存在循环引用,根据shared_ptr的引用计数原理,pa和pb都无法被正常释放

struct BW;
struct AW{
	shared_ptr<BW> pb;
	~AW()
	{
		cout<<"~AW()"<<endl;
	}
};

struct BW{
	weak_ptr<AW> pa;
	~BW()
	{
		cout<<"~BW()"<<endl;
	}
};

void Test()
{
	cout<<"Test shared_ptr and shared_ptr: "<<endl;
	shared_ptr<A> tA(new A());
	shared_ptr<B> tB(new B());
	cout<<tA.use_count()<<endl;	    //1
	cout<<tB.use_count()<<endl;		//1
	tA->pb = tB;
	tB->pa = tA;
	cout<<tA.use_count()<<endl;		//2
	cout<<tB.use_count()<<endl;		//2
}		//函数结束,两个对象的堆空间无法被释放

void Test2()
{
	cout<<"Test weak_ptr and shared_ptr: "<<endl;
	shared_ptr<AW> tA(new AW());
	shared_ptr<BW> tB(new BW());
	cout<<tA.use_count()<<endl;	    //1
	cout<<tB.use_count()<<endl;		//1
	tA->pb = tB;
	tB->pa = tA;	//不参与引用计数,tA引用计数不增加
	cout<<tA.use_count()<<endl;		//1
	cout<<tB.use_count()<<endl;		//2
}		//函数结束,两个对象的堆空间被释放
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值