C++11 智能指针的具体使用

目录
  • 智能指针的原理
    • RAII
    • 智能指针的原理
    • auto_ptr
      • 1.auto_ptr的使用及问题
    • unique_ptr
    • shared_ptr
    • shared_ptr的循环引用

智能指针的原理

RAII

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。 借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。

我们使用RAII的思想设计SmartPtr类:

template <class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

    ~SmartPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}

private:
	T* _ptr;
};

智能指针的原理

上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过->去访问所指空间中的内容 ,因此:SmartPtr模板类中还得需要将* 、->重载下,才可让其像指针一样去使用。

template <class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	T& operator*()
	{
		return *_ptr;
	}

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

    ~SmartPtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}

private:
	T* _ptr;
};

智能指针使用:

总结智能指针的原理:

  • RAII特性
  • 重载operator*和opertaor->,具有像指针一样的行为。

auto_ptr

1.auto_ptr的使用及问题

auto_ptr的头文件#include<memory>

auto_ptr的使用:

为什么此时访问sp的成员时会报错呢?我们来看看它们的地址。

我们发现在拷贝构造之后,sp管理的地址为空,而sp1管理的地址是之前sp所管理的地址,管理权发生了转移。那么上面所说的报错也很容易想通,因为sp管理的地址为空,不能进行访问。

auto_ptr的问题:当对象拷贝或者赋值后,管理权进行转移,造成前面的对象悬空。auto_ptr问题是非常明显的,所以实际中很多公司明确规定了不能使用auto_ptr。

auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份AutoPtr来了解它的原理:

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

	//拷贝:管理权转移
	AutoPtr(AutoPtr<T> &sp)
		:_ptr(sp._ptr)
	{
		sp._ptr = nullptr;
	}

	//赋值:管理权转移
	AutoPtr& operator=(AutoPtr<T> &sp)
	{
		if (this != &sp)
		{
			if (_ptr)
				delete _ptr;
			_ptr = sp._ptr;
			sp._ptr = nullptr;
		}
		return *this;
	}

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

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

	T& operator*()
	{
		return *_ptr;
	}

private:
	T* _ptr;
};

unique_ptr

为了解决拷贝或者赋值时管理权转移的问题,出现了unique_ptr。

unique_ptr解决问题的方式非常粗暴:防拷贝,也就是不让赋值和拷贝

unique_ptr的使用:

unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理:

template<class T>
class UniquePtr
{
public:

	UniquePtr(T* ptr)
		:_ptr(ptr)
	{}

	// C++11防拷贝的方式:delete
	UniquePtr(const UniquePtr<T> &) = delete;

	UniquePtr& operator=(const UniquePtr<T>&) = delete;

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

	T& operator*()
	{
		return *_ptr;
	}

	~UniquePtr()
	{
		if (_ptr)
		{
			delete _ptr;
			_ptr = nullptr;
		}
	}

private:

	//C++98防拷贝的方式:只声明不实现+声明成私有
	//UniquePtr(UniquePtr<T> const &);
	//UniquePtr& operator=(UniquePtr<T> const &);

	T* _ptr;
};

shared_ptr

c++11中提供更靠谱的并且支持拷贝的shared_ptr

shared_ptr的使用

shared_ptr中拷贝与赋值都是没有问题的。

shared_ptr的原理

  • shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  • 对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一
  • 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  • 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了。

shared_ptr中成员函数:use_count(对象数据的引用计数)

示例:

示例详解:

利用引用计数简单的实现SharedPtr,了解原理:

template<class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		,_count(new int(1))
	{}

	SharedPtr(const SharedPtr<T> &sp)
		:_ptr(sp._ptr)
		,_count(sp._count)
	{
		//计数器累加
		++(*_count);
	}

	SharedPtr& operator=(const SharedPtr<T> &sp)
	{
		//判断管理的是否是同一份资源
		if (_ptr != sp._ptr)
		{
			//计数-1,判断之前管理的资源是否需要释放
			if ((--(*_count)) == 0)
			{
				delete _ptr;
				delete _count;
			}

			_ptr = sp._ptr;
			_count = sp._count;

			//计数器累加
			++(*_count);
		}
		return *this;
	}

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

	T& operator*()
	{
		return *_ptr;
	}

	~SharedPtr()
	{
		if (--(*_count) == 0)
		{
			delete _ptr;
			delete _count;
			_ptr = nullptr;
			_count = nullptr;
		}
	}

private:
	T* _ptr;
	int* _count;//给每份资源开辟一个计数器
};

但是还存在一个线程安全的问题:

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

这里我们通过加锁来解决线程安全问题:

template<class T>
class SharedPtr
{
public:
	SharedPtr(T* ptr)
		:_ptr(ptr)
		,_count(new int(1))
		,_mutex(new mutex)
	{}

	SharedPtr(const SharedPtr<T> &sp)
		:_ptr(sp._ptr)
		,_count(sp._count)
		,_mutex(sp._mutex)
	{
		//计数器累加
		AddCount();
	}

	SharedPtr& operator=(const SharedPtr<T> &sp)
	{
		//判断管理的是否是同一份资源
		if (_ptr != sp._ptr)
		{
			//计数-1,判断之前管理的资源是否需要释放
			if (SubCount() == 0)
			{
				delete _ptr;
				delete _count;
				delete _mutex;
			}

			_ptr = sp._ptr;
			_count = sp._count;
			_mutex = sp._mutex;

				//计数器累加
			AddCount();
		}
		return *this;
	}

	//线程安全的累加器
	int AddCount()
	{
		//加锁
		_mutex->lock();
		++(*_count);
		_mutex->unlock();
		return *_count;
	}

	int SubCount()
	{
		_mutex->lock();
		--(*_count);
		_mutex->unlock();
		return *_count;
	}

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

	T& operator*()
	{
		return *_ptr;
	}

	~SharedPtr()
	{
		if (SubCount() == 0)
		{
			delete _ptr;
			delete _count;
			delete _mutex;
			_ptr = nullptr;
			_count = nullptr;
			_mutex = nullptr;
		}
	}

private:
	T* _ptr;
	int* _count;//给每份资源开辟一个计数器
	mutex* _mutex; //每一份资源有一个独立的锁
};

shared_ptr的循环引用

循环引用的场景:

struct ListNode
{
	int _data;
    shared_ptr<ListNode> _prev;
	shared_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};

void test()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);

	node1->_next = node2;
	node2->_prev = node1;
}

node1和node2两个智能指针对象指向两个节点,两个节点的引用计数都是1。node1->next指向node2,node2->prev指向node1,两个节点的引用计数都变成2。程序运行完之后,析构node1和node2,node1和node2所指向的节点引用计数分别减1,但是node1->next指向下面节点,node2->prev指向上面节点,此时,两个节点的引用计数都为1,所以两个节点不能析构。

引用计数为0时,如果要析构node1节点,就先要去析构node1中的自定义结构,然后再析构node1。也就是说node1->next析构了,node2就释放了;node2->prev析构了,node1就释放了。但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

解决方案:
在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
原理就是,node1->_next = node2和node2->_prev = node1时weak_ptr的_next和_prev不会增加node1和node2的引用计数

weak_ptr最大作用就是解决shared_ptr的循环引用

struct ListNode
{
	int _data;
    weak_ptr<ListNode> _prev;
	weak_ptr<ListNode> _next;
	~ListNode() { cout << "~ListNode()" << endl; }
};
void test()
{
	shared_ptr<ListNode> node1(new ListNode);
	shared_ptr<ListNode> node2(new ListNode);

	node1->_next = node2;
	node2->_prev = node1;
}

注意:
weak_ptr不能单独使用,可以用shared_ptr创建

	//weak_ptr错误使用
	weak_ptr<ListNode> node1(new ListNode);

	//weak_ptr正确使用
	shared_ptr<ListNode> node2(new ListNode);
	weak_ptr<ListNode> node3(node2);

到此这篇关于C++11 智能指针的具体使用的文章就介绍到这了,更多相关C++11 智能指针内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++11智能指针unique_ptr用法使用场景分析

    一.概述 C++ 标准模板库 STL(Standard Template Library) 一共给我们提供了四种智能指针:auto_ptr.unique_ptr.shared_ptr 和 weak_ptr,其中 auto_ptr 是 C++98 提出的,C++11 已将其摒弃,并提出了 unique_ptr 替代 auto_ptr.虽然 auto_ptr 已被摒弃,但在实际项目中仍可使用,但建议使用更加安全的 unique_ptr,后文会详细叙述.shared_ptr 和 weak_ptr 则是

  • c++11&14-智能指针要点汇总

    学c++的人都知道,在c++里面有一个痛点,就是动态内存的管理,就我所经历的一些问题来看,很多莫名其妙的问题,最后都发现是内存管理不当引起的. 但像java等其他一些语言则不会有这样的问题,为什么呢,因为它们有很好的处理内存的方法,比如java的垃圾回收机制,现在,我们c++终于也有了智能指针. 1. 什么是智能指针 简单地说,智能指针是用对象去管理一个资源指针,同时用一个计数器计算引用当前指针对象的个数,当管理指针的对象增加或减少时,计数器也相应加1或减1,当最后一个指针管理对象销毁时,计数器

  • c++11 新特性——智能指针使用详解

    c++11添加了新的智能指针,unique_ptr.shared_ptr和weak_ptr,同时也将auto_ptr置为废弃(deprecated). 但是在实际的使用过程中,很多人都会有这样的问题: 不知道三种智能指针的具体使用场景 无脑只使用shared_ptr 认为应该禁用raw pointer(裸指针,即Widget*这种形式),全部使用智能指针 初始化方法 class A { public: A(int size){ this->size = size; } A(){} void Sh

  • C++11智能指针中的 unique_ptr实例详解

    在前面一篇文章中,我们了解了 C++11 中引入的智能指针之一 shared_ptr 和 weak_ptr ,今天,我们来介绍一下另一种智能指针 unique_ptr . 往期文章参考: [C++11新特性] C++11 智能指针之shared_ptr [C++11新特性] C++11智能指针之weak_ptr unique_ptr介绍 unique是独特的.唯一的意思,故名思议,unique_ptr可以"独占"地拥有它所指向的对象,它提供一种严格意义上的所有权. 这一点和我们前面介绍

  • C++11 智能指针之shared_ptr代码详解

    C++中的智能指针首先出现在"准"标准库boost中. 随着使用的人越来越多,为了让开发人员更方便.更安全的使用动态内存,C++11也引入了智能指针来管理动态对象. 在新标准中,主要提供了shared_ptr.unique_ptr.weak_ptr三种不同类型的智能指针. 接下来的几篇文章,我们就来总结一下这些智能指针的使用. 今天,我们先来看看shared_ptr智能指针. shared_ptr 智能指针 shared_ptr是一个引用计数智能指针,用于共享对象的所有权也就是说它允许

  • C++11智能指针之weak_ptr详解

    如题,我们今天要讲的是 C++11 引入的三种智能指针中的:weak_ptr. 在学习 weak_ptr 之前最好对 shared_ptr 有所了解.如果你还不知道 shared_ptr 是何物,可以看看另一篇文章: [C++11新特性] C++11智能指针之shared_ptr 1.为什么需要weak_ptr? 在正式介绍weak_ptr之前,我们先来回忆一下shared_ptr的一些知识. 我们知道shared_ptr是采用引用计数的智能指针,多个shared_ptr实例可以指向同一个动态对

  • C++11新特性之智能指针(shared_ptr/unique_ptr/weak_ptr)

    shared_ptr基本用法 shared_ptr采用引用计数的方式管理所指向的对象.当有一个新的shared_ptr指向同一个对象时(复制shared_ptr等),引用计数加1.当shared_ptr离开作用域时,引用计数减1.当引用计数为0时,释放所管理的内存. 这样做的好处在于解放了程序员手动释放内存的压力.之前,为了处理程序中的异常情况,往往需要将指针手动封装到类中,通过析构函数来释放动态分配的内存:现在这一过程就可以交给shared_ptr去做了. 一般我们使用make_shared来

  • C++11 智能指针的具体使用

    目录 智能指针的原理 RAII 智能指针的原理 auto_ptr 1.auto_ptr的使用及问题 unique_ptr shared_ptr shared_ptr的循环引用 智能指针的原理 RAII RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存.文件句柄.网络连接.互斥量等等)的简单技术. 在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源. 借此,我

  • 深入了解C++智能指针的使用

    目录 一.C++11智能指针概述 二.C++98中的智能指针 三.C++11中的智能指针 1.unique_ptr 2.shared_ptr 3.weak_ptr 一.C++11智能指针概述 在C++中,动态内存的使用时有一定的风险的,因为它没有垃圾回收机制,很容易导致忘记释放内存的问题,具体体现在异常的处理上.想要释放掉抛异常的程序的一些内存,往往需要多次抛异常,这种处理方式是十分麻烦的. 智能指针的本质就是使用一个对象来接管一段开辟的空间,在该对象在销毁的时候,自动调用析构函数来释放这段内存

  • C++11中的智能指针shared_ptr、weak_ptr源码解析

    目录 1.前言 2.源码准备 3.智能指针概念 4.源码解析 4.1.shared_ptr解析 4.1.1.shared_ptr 4.1.2.__shared_ptr 4.1.3.__shared_count 4.1.4._Sp_counted_base 4.1.5._Sp_counted_ptr 4.1.6.shared_ptr总结 4.2.weak_ptr解析 4.2.1.weak_ptr 4.2.2.__weak_ptr 4.2.3.__weak_count 4.2.4.回过头看weak_

  • C++智能指针读书笔记

    最近在补看<C++ Primer Plus>第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰,一解我以前的多处困惑.C++面试过程中,很多面试官都喜欢问智能指针相关的问题,比如你知道哪些智能指针?shared_ptr的设计原理是什么?如果让你自己设计一个智能指针,你如何完成?等等--.而且在看开源的C++项目时,也能随处看到智能指针的影子.这说明智能指针不仅是面试官爱问的题材,更是非常有实用价值. C++通过一对运算符 new 和 delete 进行动态内存管理,new在动态内存中

  • C++智能指针实例详解

    本文通过实例详细阐述了C++关于智能指针的概念及用法,有助于读者加深对智能指针的理解.详情如下: 一.简介 由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete.程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见. 用智能指针便可以有效缓解这类问题,本文主要讲解参见的智能指针的用法.包括:std::auto_ptr.boost::scoped_ptr.boost::shared_p

随机推荐