C++ 智能指针shared_ptr 和 unique_ptr浅析

文章目录

  • C++ 智能指针shared_ptr 和 unique_ptr浅析
    • 1. 入门
      • 1.1 思路
      • 1.2 实现
    • 2. shared_ptr 和 unique_ptr
      • 2.1 基本的规则
      • 2.2 `shared_ptr`的使用
      • 2.3 unique_ptr
      • 2.4 管理自己的释放函数
        • 2.4.1 shared_ptr
        • 2.4.2 unique_ptr
        • 2.4.3 区别
    • 3. 总结

C++ 智能指针shared_ptr 和 unique_ptr浅析

我们知道,对于JAVA和PYTHON这种语言,会把所有的东西当做对象处理,并且不用自己管理任何内存,如果需要使用,值需要new即可。这对于普通C/C++程序员来说,简直就是福音。

在C语言中,如果你需要使用堆内存,那么需要如下使用:

void fun()
{
     
    int* p = (int*)malloc(sizeof (int));
    if (p == null)
    {
     
        //error0 
        return;
    }

    if (/*error1*/)
    {
     
        free(p);
        return;
    }

    if (/*error2*/)
    {
     
        free(p);
        return;
    }

    if (/*error3*/)
    {
     
        free(p);
        return;
    }
    free(p);
}

上面这个代码光是看起来就比较蛋疼了;因为C语言需要自己管理内存,如果函数退出,就需要管理自己分配的内存,如果一个函数退出点比较多的话每个地方都需要释放内存。这对于我这种菜鸟级别的程序员来说是很容易就忘记释放而返回的。

为了解决这个问题,C++就提出了智能指针的解决方案。

1. 入门

1.1 思路

如果从零出发,我们怎么样来自动管理自己的内存呢?当然一个很直白的想法就是利用类的析构函数自动释放内存。例如:

  1. 一个专门类专门管理指针信息。
  2. 类被析构的时候自动释放管理的指针。

这个其实就是智能指针的思路,下面来实现一下这个思路。

1.2 实现

template<typename T>
class AutoPtr
{
     
public:
	AutoPtr() : ptr(nullptr) {
     }
	explicit AutoPtr(T* p) : ptr(p) {
     }
	AutoPtr(const AutoPtr&) = delete;
	~AutoPtr()
	{
     
		if (ptr != nullptr)
		{
     
			delete ptr;
			ptr = nullptr;
		}
		std::cout << "~AutoPtr called" << std::endl;
	}
	void set(T* p)
	{
     
		if (ptr != nullptr)
		{
     
			delete ptr;
		}
		ptr = p;
	}
	operator bool() {
      return ptr != nullptr; }
	T* operator->() {
      return ptr; }
	T& operator*() {
      return *ptr; }
private:
	T* ptr;
};
void AutoPtrTest()
{
     
	AutoPtr<int> p(new int(100));
	std::cout << *p << std::endl;
	*p = 200;
	std::cout << *p << std::endl;
}

运行结果如下:

100
200
~AutoPtr called

这个就是一个最最简单的利用类的析构函数来管理堆内存的例子,当然这个例子简直简单到不行了,简单到基本不能太大的使用;但是不用急,C++的智能指针已经提供了全部的解决方案了。

2. shared_ptr 和 unique_ptr

2.1 基本的规则

shared_ptr的意思是这个指针是共享的,大家都可以使用;这个里面使用一个引用计数来管理使用者的数目,例如:

  1. A使用了一个内存对象,此时引用计数为1.
  2. B引用了同一个内存对象,此时引用计数为2.
  3. C也引用了同一个内存对象,此时引用计数为3.
  4. 此时B不使用内存了,释放了shared_ptr,此时引用计数变为2.
  5. 接着A也不使用了,释放了shared_ptr,此时引用计数变为1.
  6. 最后C也不使用了,释放了shared_ptr,此时引用计数变为0,当引用计数为0之后,释放内存对象占用的内存。

unique_ptr的意思是这个指针只有我自己能够使用,别人都不能用,所以unique_ptr不支持拷贝。

2.2 shared_ptr的使用

template<class _Ty>
class shared_ptr
	: public _Ptr_base<_Ty>
{
     
}
template<class _Ty>
	class _Ptr_base
{
     
private:
	element_type * _Ptr{
     nullptr};
	_Ref_count_base * _Rep{
     nullptr};   
}

这里对象和引用计数都是使用指针来操作,这样的话,新建对象的时候就可以简单的值拷贝(拷贝指针)就可以了。

shared_ptr提供两种方式初始化:

  1. 构造的时候提供指针。
  2. std::make_shared 构造。
// make_shared example
#include 
#include 

int main () {
     

  std::shared_ptr<int> foo = std::make_shared<int> (10);
  // same as:
  std::shared_ptr<int> foo2 (new int(10));

  auto bar = std::make_shared<int> (20);

  auto baz = std::make_shared<std::pair<int,int>> (30,40);

  std::cout << "*foo: " << *foo << '\n';
  std::cout << "*bar: " << *bar << '\n';
  std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';

  return 0;
}

2.3 unique_ptr

这个是指一个指针只能被一个类管理,也就是说,这个类不支持普通的拷贝和赋值(因为底层直接使用浅拷贝,会导致问题)。

template<class _Ty,
	class _Dx>	// = default_delete<_Ty>
	class unique_ptr
		: public _Unique_ptr_base<_Ty, _Dx>
{
     

}

2.4 管理自己的释放函数

无论是shared_ptr还是unique_ptr都是使用delete来释放内存的,但是如果我们不是使用delete来释放内存呢?那就需要自己设置释放函数了。

对于释放函数shared_ptrunique_ptr是有点不同的。

2.4.1 shared_ptr

这个类的构造函数如下:

//default (1)	
constexpr shared_ptr() noexcept;

//from null pointer (2)	
constexpr shared_ptr(nullptr_t) : shared_ptr() {
     }

//from pointer (3)	
template <class U> explicit shared_ptr (U* p);

//with deleter (4)	
template <class U, class D> shared_ptr (U* p, D del);

也就是说,我们可以在构造函数中设置释放函数,例如如果我们想用shared_ptr管理HANDLE句柄就可以如下使用:

void handle_ptr_delete(PHANDLE pHandle)
{
     
	CloseHandle(*pHandle);
	delete pHandle;
}

std::shared_ptr<HANDLE> make_handle_ptr(HANDLE Handle)
{
     
	PHANDLE pHandle = new HANDLE(Handle);
	return std::shared_ptr<HANDLE>(pHandle, handle_ptr_delete);
}
int main(int args, char* argv[])
{
     
	HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	std::shared_ptr<HANDLE> Handle = make_handle_ptr(hEvent);
	SetEvent(*Handle);
	return 0;
}

2.4.2 unique_ptr

unique_ptr的声明如下:

template<class _Ty,
	class _Dx>	// = default_delete<_Ty>
	class unique_ptr
		: public _Unique_ptr_base<_Ty, _Dx>
{
     

}

从声明我们可以知道, unique_ptr 的删除参数是在模板参数中的。

例如使用如下:

void handle_ptr_delete(PHANDLE pHandle)
{
     
	CloseHandle(*pHandle);
	delete pHandle;
}

int main(int args, char* argv[])
{
     
	HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
	std::unique_ptr<HANDLE, decltype(handle_ptr_delete)*> Handle(new HANDLE(hEvent), handle_ptr_delete);
	SetEvent(*Handle);
	return 0;
}

2.4.3 区别

shared_ptrunique_ptr的释放函数使用的时候还是有不同的。那么为什么C++为什么会带来这种差异呢?我自己猜测主要是如下原因:

  1. shared_ptr主要是灵活,shared_ptr这个指针在使用的时候可以随意改变;那么将删除函数设置为成员,使用上面带来了很大的便利,例如template void reset (U* p, D del);直接设置相关信息。
  2. unique_ptr这个东西比较固定,无法拷贝和其他操作,主要是用来管理单个内存对象,所有就不用那么灵活的使用了,为了性能上面的考虑unique_ptr就直接使用了模板参数来设置删除函数(毕竟模板参数在编译期间确定,性能会有所提升)。

3. 总结

通过shared_ptrunique_ptr我们可以方便的管理堆内存了;更加重要的一个问题是,不用担心底层抛出异常而跳出内存释放的逻辑(因为析构函数永远都会被调用)。

其实除了shared_ptrunique_ptr之外还有weak_ptr,weak_ptr比较特殊,下面专门抽一篇文章来讲解。

你可能感兴趣的:(C++语言编程,c++)