c++ ---智能指针

为什么要使用智能指针?
智能指针的作用就是管理一个指针,因为存在以下的情况:申请的空间在函数结束的时候忘记释放,造成内存泄漏。使用智能指针可以很大程度上避免这一问题,因为智能指针就是一个类,当超出了类的作用域之后,类就会自动调用析构函数,析构函数就会自动释放资源。所以析构函数的作用就是在函数结束时自动释放空间,不需要手动释放内存空间。

一.RAII
  • (1)RAII,也称为“资源获取就是初始化”,是c++等编程语言常用的管理资源、避免内存泄露的方法。
  • (2)在对象构造的时候获取资源,接着控制对资源的访问使之在对象的声明周期内始终保持有效,最后在对象析构的时候释放资源。
  • (3)我们实际上把管理一份资源的责任交给了一个对象,这样做有两大好处。
    不需要显示的释放资源
    采用这种方式,对象所需的资源在生命周期内始终保持有效。
二.auto_ptr

auto_ptr的实现原理:管理权转移的意思。但是存在内存崩溃的问题。

#include<iostream>
using namespace std;

template<class T>
class AutoPtr
{
public:
	AutoPtr(T* ptr = NULL)
		:_ptr(ptr)
	{}

	~AutoPtr()
	{
		if (_ptr)
			delete _ptr;
	}

	//一旦发生了拷贝,就将p中的资源转移到当前对象中,
	//然后让p与其所管理的对象断开联系
	//这样就解决了一块空间被多个对象使用而造成内存崩溃问题。
	AutoPtr(AutoPtr<T>& p)
		:_ptr(p._ptr)
	{
		p._ptr = nullptr;
	}

	AutoPtr<T>& operator=(AutoPtr<T>& p)
	{
		if (this != &p)
		{
			// 释放当前对象中资源 
			if (_ptr)
				delete _ptr;

			// 转移ap中资源到当前对象中 
			_ptr = p._ptr;
			p._ptr = nullptr;
		}
		return *this;
	}

	T& operator*(){ return *_ptr; }
	T* operator->(){ return _ptr; }
private:
	T* _ptr;
};

int main()
{
	AutoPtr<int> p(new int (10));

	//这里拷贝之后把p的对象指针赋空了,到中p对象悬空
	//通过p对象进行资源访问就会出现问题。
	AutoPtr<int> copy(p);
	
	copy = p;
	system("pause");
	return 0;
}
三.unique_ptr

unique_ptr实现独占式拥有或严格拥有的概念,保证同一时间内只有一个智能指针可以指向该对象。

unique_ptr<string> p1(new string ("auto"));
unique_ptr <string> p4;
p4 = p3;
//此时会报错
//编译器认为p4 = p3非法,避免了p3不再指向有效数据的问题。因此unique_ptr比auto_ptr更安全

当程序试图将一个unique_ptr赋值给另一个的时候,如果源unique_ptr是一个临时右值,编译器允许这么做;如果源unique_ptr将存在一段时间,编译器将禁止这么做。

unique_ptr<string> p1(new string ("hello world"));
unique_ptr<string> p2;
p2 = p1;                                          // #1 not allowed

unique_ptr<string> p;
p3 = unique_ptr<string>(new string ("You"));           // #2 allowed

其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。

注:如果确实想执行类似与#1的操作,要安全的重用这种指针,可给它赋新值。C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如:

unique_ptr<string> p1, p2;
p1 = demo("hello");
p2 = move(ps1);
p1 = demo("alexia");
cout << *p2 << *p1 << endl;
四.shared_ptr

shared_ptr实现共享式拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。

成员函数:

use_count 返回引用计数的个数

unique 返回是否是独占所有权( use_count 为 1)

swap 交换两个 shared_ptr 对象(即交换所拥有的对象)

reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少

get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如 shared_ptr sp(new int(1)); sp 与 sp.get()是等价的

1.shared_ptr的线程安全问题

  • (1)智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或者–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2.这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能在指针中引用计数++,–是需要上锁的,也就是说引用计数的操作是线程安全的。
  • (2)智能指针管理的对象存放在栈上,两个线程同时去访问,会导致线程安全问题。

2.shared_ptr的循环引用

struct ListNode 
{    
	int _data;    
	shared_ptr<ListNode> _prev;    
	shared_ptr<ListNode> _next;
 
	~ListNode()
	{ 
		cout << "~ListNode()" << endl; 
	} 
};
 
int main()
{
 	shared_ptr<ListNode> node1(new ListNode);    
 	shared_ptr<ListNode> node2(new ListNode);    
 	cout << node1.use_count() << endl;    
 	cout << node2.use_count() << endl;
 
    node1->_next = node2;    
    node2->_prev = node1;
 
    cout << node1.use_count() << endl;    
    cout << node2.use_count() << endl;
 
    return 0; 
}

  • (1)node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  • (2)node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  • (3)node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。 * * (4)也就是说_next析构了,node2就释放了。
  • (5)也就是说_prev析构了,node1就释放了。
  • (6)但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2 成员,所以这就叫循环引用,谁也不会释放。
//解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了 
// 原理就是,node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加 node1和node2的引用计数。 
struct ListNode 
{    
	int _data;    
	weak_ptr<ListNode> _prev;    
	weak_ptr<ListNode> _next; 
  
	~ListNode()
	{ 
		cout << "~ListNode()" << endl; 
	} 
};
 
int main() 
{    
	shared_ptr<ListNode> node1(new ListNode);    
	shared_ptr<ListNode> node2(new ListNode);    
	cout << node1.use_count() << endl;    
	cout << node2.use_count() << endl;
 
    node1->_next = node2;    
    node2->_prev = node1;
 
    cout << node1.use_count() << endl;    
    cout << node2.use_count() << endl;
 
    return 0; 
}

五.weak_ptr

weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的 shared_ptr. weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

注意的是我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(); 英文pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:shared_ptr p = pa->pb_.lock(); p->print();

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值