C++——智能指针

目录

智能指针出现的原因

避免内存泄漏

智能指针的使用及原理

RAII

智能指针的原理

auto_ptr

unique_ptr

shared_ptr

weak_ptr

定制删除器


智能指针出现的原因

        在上一篇异常的介绍中就提到了,如果申请了一块空间,因为抛异常导致资源没有释放,这就会导致内存泄漏。

        内存泄漏就是因为疏忽或错误造成程序没有释放不再使用的内存。这并不是说内存真的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

        上一段简单的说明就是,内存泄漏就是指针丢失了,但是空间是丢失不了的,造成空间浪费。

        对于短期运行的程序出现内存泄漏,没有什么影响,进程结束,内存就释放了;对于长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

避免内存泄漏

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。
  2. 采用RAII思想或者智能指针来管理资源。这就是接下来要说的。
  3. 出问题了使用内存泄漏工具检测,检测工具的原理就是申请空间就用一个容器记录下来,释空间的时候就从容器中删除,这样就可以检查哪些是内存泄漏的资源。

智能指针的使用及原理

RAII

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

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

template
class SmartPtr
{
	SmartPtr(T* ptr = nullptr) // 使用指针构造一个对象
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr; // 在对象生命周期结束后自动调用析构函数释放资源
	}
private:
	T* _ptr;
};

智能指针的原理

        上面的代码还不能算是智能指针,因为它还不具有指针的行为。指针可以解引用,也可以通过 -> 去访问所指空间中的内容,因此类中还得需要重载 * 和 -> ,这样才可以像指针一样去使用。

template
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr) // 使用指针构造一个对象
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		if (_ptr)
			delete _ptr; // 在对象生命周期结束后自动调用析构函数释放资源
	}
	T& operator&()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。

auto_ptr的实现原理:管理权转移的思想

C++——智能指针_第1张图片

        假如这里有一个A类,把它托管给auto_ptr管理,当我要赋值的时候,就像迭代器一样,我不需要让他深拷贝,他是一个指针,负责指向就好了,如果是浅拷贝,赋值之后两个对象指向同一块空间,析构就会析构两次,为了解决这个问题,auto_ptr会把原来对象的权限转移给新的对象,但是要是使用原来的对象就会出错,所以它使用的是管理权转移,说白了也就是把旧的指针赋值过去,再置为空,但是这种做法很不负责,平常是不用的。

unique_ptr

因为auto_ptr不是那么好用,C++11中就有了unique_ptr。

unique_ptr的实现原理:简单粗暴的防拷贝。下面就是简单实现一下。

template
class unique_ptr
{
public:
	unique_ptr(T* ptr = nullptr)
		:_ptr(ptr)
	{}

	// 防止拷贝 C++11的方法
	unique_ptr(unique_ptr& up) = delete;
	unique_ptr& operator=(unique_ptr& up) = delete;

	// C++98使用的是私有拷贝构造、赋值,只声明不实现

	~unique_ptr()
	{
		if (_ptr)
			delete _ptr;
	}
	T& operator&()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
};

但是这样简单的防止拷贝也不是办法,它只适用于不需要拷贝的。

shared_ptr

如果就是想要拷贝呢?C++11中开始提供更靠谱的并且支持拷贝的shared_ptr。

shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源。有几个资源指向这块空间就是几,释放的时候,如果不是最后一个,引用计数就 - - ,如果是最后一个再释放

template
class shared_ptr
{
public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _pCount(new int(1)) // 初始化为1
	{}

	~shared_ptr()
	{
		if (--(*_pCount) == 0)
		{
			delete _ptr;
			delete _pCount;
		}
	}

	// 拷贝构造
	shared_ptr(const shared_ptr& sp)
		:_ptr(sp._ptr)
		, _pCount(sp._pCount)
	{
		(*_pCount)++;
	}

	// 赋值
	shared_ptr& operator=(const shared_ptr& sp)
	{
		if (sp._ptr != _ptr) // 不需要自己给自己赋值,也不需要给指向同一块的赋值
		{
			if (--(*_pCount) == 0) // 原来的是不是最后一个,是的话就释放
			{
				delete _ptr;
				delete _pCount;
			}

			_ptr = sp._ptr;
			_pCount = sp._pCount;
			(*_pCount)++;
		}
		return *this;
	}
    T* get()
    {
        return _ptr;
    }
	T& operator&()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pCount; // 引用计数
};

        shared_ptr看似很完美,但是它还有一个问题,那就是循环引用,接下来我们就来看看什么是循环引用。

struct Node
{
	int _data;
	std::shared_ptr _prev;
	std::shared_ptr _next;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

int main()
{
	// 这里不能使用=赋值,因为shared_ptr构造使用的explicit,不可以有隐式类型的转换
	std::shared_ptr n1(new Node);
	std::shared_ptr n2(new Node);
	// 如果只把next修改,最后会析构两次,如果把next和prev都修改,变成循环链表最后就没有析构
	n1->_next = n2;
	// n2->_prev = n1;
	return 0;
}

C++——智能指针_第2张图片

这就是循环引用,谁也释放不了,这样就出现了weak_ptr。

weak_ptr

        weak_ptr不是常规的智能指针,它没有RAII,不支持直接管理资源,weak_ptr主要用shared_ptr构造,用来解决shared_ptr循环引用问题。

C++——智能指针_第3张图片

        next和prev是weak_ptr时,他不参与资源释放管理,也不增加引用计数,但是可以访问和修改资源,所以就不存在循环引用。

struct Node
{
	int _data;
	std::weak_ptr _prev;
	std::weak_ptr _next;

	~Node()
	{
		cout << "~Node()" << endl;
	}
};

下面就简单实现一下weak_ptr。

// 为了解决shared_ptr循环引用问题
template
class weak_ptr
{
public:
	weak_ptr()
		:_ptr(nullptr)
	{}
	weak_ptr(const shared_ptr& sp)
		:_ptr(sp.get())
	{}
	weak_ptr(const weak_ptr& wp)
		:_ptr(wp.get())
	{}
	weak_ptr& operator=(const shared_ptr& sp)
	{
		_ptr = sp.get();
		return *this;
	}

	T* get()
	{
		return _ptr;
	}

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

定制删除器

        如果一个对象是通过new[]出来的,那么用delete就会出问题,一定要使用delete[]。对于自定义类型,new[]实际是malloc和n次构造函数,它会在开空间的时候会在头的位置多开一个指针,使用这个指针存放调用了几次构造函数,等到delete[]的时候,先向前偏移到该位置,再释放资源,如果继续使用delete,指针不会偏移,释放的位置就会出错。这在vs下是这样实现的,所以使用new和delete的时候一定要匹配。

        智能指针中提供了一个定值删除器,unique_ptr和shared_ptr也使用了default_ptr。

当然,它也可以是一个仿函数,使用的时候定义匿名对象就可以了。

template 
struct DeleteArr
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

template 
struct FreeArr
{
	void operator()(T* ptr)
	{
		free(ptr);
	}
};

int main()
{
	std::shared_ptr n1(new Node[5], DeleteArr());
	std::shared_ptr n2(new int[5], DeleteArr());
	std::shared_ptr n3((int*)malloc(sizeof(4)), FreeArr());

	return 0;
}

既然可以使用仿函数,那么自然可以使用lambda表达式。

int main()
{
	std::shared_ptr n1(new Node[5], [](Node* ptr) { delete[] ptr; });
	std::shared_ptr n2(new int[5], [](int* ptr) { delete[] ptr; });
	std::shared_ptr n3((int*)malloc(sizeof(4)), [](int* ptr) { free(ptr); });

	return 0;
}

        库中的模板参数还有一个类型,这就是删除器的类型,在创建对象的时候就可以指定是哪个删除器, 可以写一个默认的,也可以自己实现一个。这里就是简单说一下,库里的实现方式还是很麻烦的。

	template  // 默认的定值删除器
	struct Delete
	{
		void operator()(T* ptr)
		{
			delete ptr;
		}
	};

	template>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pCount(new int(1)) // 初始化为1
		{}

		void Release()
		{
			if (--(*_pCount) == 0)
			{
				// delete _ptr;
				D del;
				del(_ptr);
				delete _pCount;
			}
		}

		~shared_ptr()
		{
			Release();
		}

		// 拷贝构造
		shared_ptr(const shared_ptr& sp)
			:_ptr(sp._ptr)
			, _pCount(sp._pCount)
		{
			(*_pCount)++;
		}

		// 赋值
		shared_ptr& operator=(const shared_ptr& sp)
		{
			if (sp._ptr != _ptr) // 不需要自己给自己赋值,也不需要给指向同一块的赋值
			{
				Release();

				_ptr = sp._ptr;
				_pCount = sp._pCount;
				(*_pCount)++;
			}
			return *this;
		}

        //...
	}

        修改了一下代码,可以使用默认的删除器也可以自己写一个删除器,假如DeleteArr就是自己写的,因为它释放数组,默认的就让他直接释放指针。

int main()
{
	shared_ptr> n1(new Node[5]);
	shared_ptr> n2(new int[5]);

	return 0;
}

你可能感兴趣的:(C++,c++)