从源码理解智能指针(一)——auto_ptr、unique_ptr

本文深入解析C++中的智能指针,包括auto_ptr和unique_ptr。首先介绍auto_ptr的基本操作,如构造、拷贝赋值、析构,以及如何实现指针行为。然后转向unique_ptr,分析其构造、删除器、移动构造和赋值操作,强调其“独占性”原则。unique_ptr还支持自定义删除器,提供更灵活的资源管理。通过对源码的解读,揭示了智能指针如何实现资源安全管理和避免内存泄漏。
摘要由CSDN通过智能技术生成

目录

auto_ptr

构造函数

拷贝赋值

让auto_ptr对象具有指针的行为

析构函数

unique_ptr

_Unique_ptr_base

remove_reference

_Get_deleter_pointer_type

_Unique_ptr_base的第三个模板参数

构造函数

无参/NULL构造

用管理对象实例构造

用管理对象实例及删除器实例构造

用另一个unique_ptr进行构造(移动构造)

release函数

赋值重载

reset函数

资源交换swap

获取资源指针get

其它的一些重载

析构函数

删除器deleter

管理数组资源的unique_ptr

管理数组资源的删除器

使用自定义删除器

总结


       在C++中,是通过new和delete来进行动态内存管理的。使用new从堆上分配空间,使用delete来释放空间。由于这两个工作都不是自动进行的,因此动态内存的管理是很困难的,因为无法确保能在正确的时间对已申请的动态内存进行释放:有可能申请之后忘记释放,这样就会造成内存泄漏;也有可能提前就进行了释放,这就会引起后续的非法访问操作。

       智能指针,就是为了更简单、更安全地管理动态内存。智能指针是基于“以对象管理资源”的观念,主要有两个关键点:获得资源后立刻放进管理对象(也就是“资源取得时机便是初始化时机”,即RAII),并且管理对象调用析构函数时确保资源被释放。这里所说的“管理对象”,就是智能指针。实际上,智能指针就是具有指针行为的对象

(以下源码均源于VS2013自带STL)

auto_ptr

       auto_ptr虽然在C++11中已经被弃用,但是通过它来理解智能指针还是非常有帮助的,它定义在xmemory文件中。

构造函数

       auto_ptr类提供了三种构造方式:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;

	explicit auto_ptr(_Ty *_Ptr = 0) _THROW0()   //通过指针构造
		: _Myptr(_Ptr)
		{	// construct from object pointer
		}

	auto_ptr(_Myt& _Right) _THROW0()   //拷贝构造
		: _Myptr(_Right.release())
		{	// construct by assuming pointer from _Right auto_ptr
		}

        template<class _Other>
		auto_ptr(auto_ptr<_Other>& _Right) _THROW0()  //不同模板类型auto_ptr对象构造
		: _Myptr(_Right.release())
		{	// construct by assuming pointer from _Right
		}
	......
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

        三者分别如下所示:

int * x = new int(3);

std::auto_ptr<int> ptr0(x);    //第一种构造

std::auto_ptr<int> ptr1(ptr0);  //第二种构造

std::auto_ptr<const int> ptr2(ptr0);    //第三种构造

对于第一种构造,是直接通过一个指针来构造,用传入的指针初始化成员指针_Myptr变量;

对于第二种构造,是通过另一个auto_ptr对象来构造,并用传入的对象调用release函数的返回值来初始化_Myptr;

对于第三种构造,是通过不同模板类型的auto_ptr对象来构造,也是将传入的对象调用release函数的返回值来初始化_Myptr。

       auto_ptr的成员函数release定义如下:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;
	......
	_Ty *release() _THROW0()
		{	// return wrapped pointer and give up ownership
		_Ty *_Tmp = _Myptr;
		_Myptr = 0;
		return (_Tmp);
		}
	......
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

        在release函数中,会先保存指针成员_Myptr的值到_Tmp中,然后将_Myptr置0后,返回_Tmp。回到构造函数中,当使用auto_ptr对象来构造时,传入的对象参数的_Myptr就会被置为0,也就是NULL,而它原来的值则会被保留到新构造的对象中。相当于换了一个指针指向原地址。这一点充分体现了auto_ptr的要求:一个物件只能有一个拥有者,严禁一物二主(《C++标准程序库》)。

拷贝赋值

        auto_ptr通过重载赋值运算符来实现拷贝赋值。赋值重载定义如下:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;
	......
	template<class _Other>
		_Myt& operator=(auto_ptr<_Other>& _Right) _THROW0()
		{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
		}
	_Myt& operator=(_Myt& _Right) _THROW0()
		{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
		}
        ......
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

       和前面的构造类似,这里的拷贝赋值也重载了两种:一种是相同模板类型下auto_ptr对象的拷贝赋值,另一种则是不同模板类型下auto_ptr对象的拷贝赋值。这两种的实现都是相同的,都会调用release函数,相当于释放了传入对象的拥有权,并将拥有权转移到被赋值的左值对象上。这里还调用了一个reset函数,该函数定义如下:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;
	......
	void reset(_Ty *_Ptr = 0)
		{	// destroy designated object and store new pointer
		if (_Ptr != _Myptr)
			delete _Myptr;
		_Myptr = _Ptr;
		}
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

       reset函数的作用很简单:如果传入的指针与当前的_Myptr指针不同,那么就释放_Myptr所指向的内存,不管是否相同,最后都会将传入的指针参数赋值给_Myptr。这就相当于让auto_ptr的指针成员_Myptr指向传入指针参数所指向的地方。

       也就是说,拷贝赋值的作用,就是让当前auto_ptr指向传入赋值的参数指向的地方,并且重置该参数指针为NULL,相当于拥有权的转移,也体现了“一个物件只能有一个拥有者”的设计思想。

       这里说了一个“让auto_ptr指向的地方”,显然,auto_ptr只是一个类/对象,它的“指向”都是通过其成员指针变量指针_Myptr实现的,按道理来说auto_ptr不应该有“指向”的说法,但是auto_ptr又却是被称为智能指针,这就是因为作为类/对象的auto_ptr确实能有指针一样的行为。

让auto_ptr对象具有指针的行为

       简单来说,就是要让auto_ptr对象用起来像指针。举个例子,定义了一个auto_ptr对象:auto_ptr<type>p(new type());通过*p可以访问到用来初始化的type类型的对象,并且p->xxx则可以访问用于初始化的type类型的对象中的成员。可以看到,这两种方式完全把p当做了一个指针来使用,并且这个指针就指向用来初始化auto_ptr的参数。

        前面说过,auto_ptr的指针行为,都是通过指针_Myptr来实现的,因此,要想让*p和p->有意义,只需要指针_Myptr来重载“*”和“->”即可。auto_ptr是这样做的:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;

	......

	_Ty& operator*() const _THROW0()
		{	// return designated value
		return (*get());
		}

	_Ty *operator->() const _THROW0()
		{	// return pointer to class object
		return (get());
		}

	_Ty *get() const _THROW0()
		{	// return wrapped pointer
		return (_Myptr);
		}

	......

private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

        先来看这里有一个get函数,调用该函数得到_Myptr的值。重载“*”,使得“*p”返回的实际上是*_Myptr,而“p->”返回的则是_Myptr本身。举个例子,如果auto_ptr的模板类型为class A,那么_Myptr就是指向一个A object的指针,那么*_Myptr就是这个object,因此就会有以下结果:

      由图可知,重载“*”和“->”,就可以通过auto_ptr对象去访问它所指向的对象,当然,如果auto_ptr本身模板类型就是int、double这样的,那么(*p)就是对应的int型变量的值,而p->理应是int型变量的地址,但是由于“->”很特殊,因此光是一个“p->”并不能通过编译,需要“p.operator->()”才是int型变量的地址。

       这样,就使得auto_ptr的行为更像指针。

析构函数

       智能指针作为“用对象管理资源”的工具,需要实现两个关键:在资源获取时就是初始化,这一点在构造函数中得以体现;第二点则是在析构函数中释放资源。下面就来看看auto_ptr的析构函数:

template<class _Ty>
	class auto_ptr
		{	// wrap an object pointer to ensure destruction
public:
	typedef auto_ptr<_Ty> _Myt;
	typedef _Ty element_type;
	......
	~auto_ptr() _NOEXCEPT
		{	// destroy the object
		delete _Myptr;
		}
	......
private:
	_Ty *_Myptr;	// the wrapped object pointer
	};

       在析构函数中,只做了一件事:释放_Myptr指向的地址。这样,就保证了资源在auto_ptr对象构造时获取,在auto_ptr对象析构时释放,实现“智能”管理资源。

       由上可知,auto_ptr是一种独占性的指针,但是它最大的问题在于:向用户提供了拷贝、赋值等操作,而这些操作的最后都会把原对象持有的指针置为NULL,却不给用户任何提示。这就会导致用户不小心对auto_ptr对象进行了拷贝、赋值等操作,却没有任何提示,如果用户又不小心使用了原对象,那么就很有可能引起问题,而这种问题是很难发现的。并且从另方面来说,既然设计原则是“严禁一物二主”,那么为何还提供拷贝、赋值这样的操作呢?这显然也是不符合“独占”语义的,更符合“交换拥有权”的语义,而unique_ptr则解决了这一问题。

unique_ptr

       unique_ptr是auto_ptr从C++11开始的一种替代品,它也延续了auto_ptr“严禁一物二主”的原则,不过unique_ptr更符合语义的一点,是直接禁止了拷贝构造和赋值重载,如下所示:

template<class _Ty,class _Dx>	
class unique_ptr
{
        ......
    unique_ptr(const _Myt&) = delete;       //禁用拷贝构造
    _Myt& operator=(const _Myt&) = delete;  //禁用赋值重载
}

      在分析unique_ptr源码之前,还有一些是需要知道的。

      unique_ptr与auto_ptr相比,

unique_ptr 是 C++ 标准库中的一个智能指针类,用于管理动态分配的对象,确保在不再需要时正确释放内存。虽然我无法提供完整的 unique_ptr 源码,但我可以给您一个简单的示例来说明其工作原理。 ```cpp template <typename T> class unique_ptr { public: // 构造函数 explicit unique_ptr(T* ptr = nullptr) : m_ptr(ptr) {} // 禁用拷贝构造函数和赋值操作符 unique_ptr(const unique_ptr<T>&) = delete; unique_ptr<T>& operator=(const unique_ptr<T>&) = delete; // 移动构造函数和移动赋值操作符 unique_ptr(unique_ptr<T>&& other) noexcept : m_ptr(other.release()) {} unique_ptr<T>& operator=(unique_ptr<T>&& other) noexcept { if (this != &other) { reset(other.release()); } return *this; } // 析构函数 ~unique_ptr() { reset(); } // 获取原始指针 T* get() const { return m_ptr; } // 重载箭头操作符 T* operator->() const { return m_ptr; } // 重载解引用操作符 T& operator*() const { return *m_ptr; } // 重置智能指针 void reset(T* ptr = nullptr) { if (m_ptr) { delete m_ptr; } m_ptr = ptr; } // 释放指针所有权,返回原始指针并将智能指针置空 T* release() { T* ptr = m_ptr; m_ptr = nullptr; return ptr; } private: T* m_ptr; }; ``` 上述代码是一个简化的 unique_ptr 实现,它包含了构造函数、析构函数、移动构造函数和移动赋值操作符,以及一些常用的成员函数,如 get、reset 和 release。注意,在这个示例中,并没有实现拷贝构造函数和赋值操作符,因为 unique_ptr 是独占所有权的智能指针,不允许进行拷贝操作。 需要注意的是,上述示例只是简化的实现,并不包含 unique_ptr 的完整功能和异常安全性。实际的 unique_ptr 实现可能更加复杂,考虑了线程安全性、对数组类型的支持等等。 希望以上示例能够帮助您理解 unique_ptr 的基本原理和用法。如有更多疑问,请随时追问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值