C++智能指针:auto_ptr详解

指针,相信大家并不陌生。无论是我们在进行查看内存还是在修改字符串,我们都会用到指针。

最常见的情况则是我们使用malloc或者new申请到了一块内存,然后用一个指针来保存起来。我们都知道有内存的申请那就必须要对它进行释放的处理,否则会造成最严重的后果——内存泄漏。一个或者两个申请的内存我们或许不会忘记去释放,但是如果成千上万行代码,你还能记得住哪个释放了哪个没有释放吗?

而智能指针就是为了解决这个问题而产生的。最开始智能指针是在boost库中的,随着时间发展现在已经成为了C11的特性。(虽然我们本篇要介绍的最基础的auto_ptr在C++11中已经被unique_ptr替代了。。)

 

  • 智能指针的基本原理

智能指针其实是一个类,可以通过将普通指针作为参数传入智能指针的构造函数实现绑定。只不过通过运算符重载让它“假装”是一个指针,也可以进行解引用等操作。既然智能指针是一个类,对象都存在于栈上,那么创建出来的对象在出作用域的时候(函数或者程序结束)会自己消亡,所以在这个类中的析构函数中写上delete就可以完成智能的内存回收。

 

  • Auto_ptr详解

使用时,需要包含头文件:memory。

auto_ptr,作为智能指针的始祖,能基本实现我们所期望的功能。而且设计简单源码易懂,虽然缺陷众多,但作为了解智能指针的研究对象还是十分合适的。

首先我们先来写一个测试类用于分析。

#include 
#include 

using namespace std;

class Test
{
public:
    Test(int param = 0)                    //调用时,可以指定是第几个对象。默认第0个
    {
        num = param;
        cout << "Simple:" << num << endl;
    }
    
    ~Test()                                //析构时会指出第几个对象被析构。
    {
        cout << "~Simple:" << num << endl;
    }

    void PrintSomething()                  //输出附加的字符串方法。
    {
        cout << "PrintSomething:" << info_extend.c_str() << endl;
    }

    int num;                               //代表这个对象的序号。
    string info_extend;                    //附加的字符串。

};

接下来,我们通过几个测试函数来实验一下auto_ptr的基本功能,再来了解一下它的弊端。

                                   I.Auto_ptr的基本功能

void TestAutoPtr1()
{
    auto_ptr my_auto (new Test(1));    //绑定一个Test类的新建对象
    if(my_auto.get())                        //get函数用来显式返回它拥有的对象指针,此处判非空
    {
        my_auto->PrintSomething();
        my_auto.get()->info_extend = "Addition"; //对类内成员进行操作
        my_auto->PrintSomething();              //看是否成功操作
        (*my_auto).info_extend += " other";     //实验解引用操作
        mt_auto->PrintSomething();
    }
}

运行结果:

C++智能指针:auto_ptr详解_第1张图片

我们可以看到在绑定时输出Simple:1,之后也能正常实现Test类中的功能,同时my_auto可以通过get方法进行裸指针赋值以及使用*进行解引用操作,与普通指针无异。最后函数结束后,在调用析构函数的同时auto_ptr的析构函数也将Test类的对象delete掉,我加入的内存泄漏探测工具也显示No memory leaks detected(没有检查测到内存泄漏).

                                   II.Auto_ptr的弊端1

void TestAutoPtr2()
{
    auto_ptr my_auto1 (new Test(1));
    if(my_auto1.get())
    {
        (*my_auto1).info_extend = "Test";    //先赋予一个初始值作为区别两个指针的标志
        
        auto_ptr my_auto2;
        my_auto2 = my_auto1;
        my_auto2->PrintSomething();            //打印发现成功转移
        my_auto1->PringSomething();            //崩溃
    }
}

在这个程序的最后一步会崩溃,罪魁祸首就是my_auto2 = my_auto1语句。

autp_ptr对赋值运算符重载的实现是reset(Myptr.release()),即reset和release函数的组合。release会释放所有权,reset则是用于接受所有权。具体代码请看最后的auto_ptr源码。my_auto1被release之后已经被置0(内部实现),所以调用函数当然会出错。

                                  III.Auto_ptr的弊端2

void Fun(auto_ptr  ap)
{
    *ap;
}

void TestAutoPtr3()
{
    auto_ptr my_auto(new Test(1));
    Fun(my_auto);
    my_auto->PrintSomething();        //崩溃
}

这个错误非常隐蔽,基本很难发现。可以看到在调用Fun函数之后,my_auto竟然又被置空了,所以导致调用PrintSomething方法崩溃。说到底,还是因为我们在Fun函数的参数列表中使用的是值传递。值传递的情况下在调用Fun函数时,由于不会对本身进行修改,所以会产生一个临时对象来接受参数。是不是熟悉的问题?又出现了赋值运算符,因为auto_ptr对赋值运算符重载的关系原指针就被置空了。只不过这次Fun函数结束后临时对象也被抛弃,my_auto也置空,保存的那块内存就彻底丢失了,也造成了内存泄漏,这可是真正上的危机。(相比之下TestAutoPtr2函数中的情况,好歹内存还被my_auto2保存着)

解决的方法也很简单,Fun函数参数改为引用传递就可以了。

                                       IV.Auto_ptr的弊端3

void TestAutoPtr4()
{
    auto_ptr  my_auto(new Test(1));
    if(my_auto.get())
    {    
        //my_auto.release();        //内存泄漏
        my_auto.reset();
    }
}

直接调用release就会导致内存泄漏。在不了解release和reset的实现时,经常会出现不知道使用哪个而导致内存泄漏的问题。在此我们就对这两个函数进行分析和区别。

_Ty *release() _THROW0()
		{	// return wrapped pointer and give up ownership
		_Ty *_Tmp = _Myptr;
		_Myptr = 0;
		return (_Tmp);
		}

	void reset(_Ty *_Ptr = 0)
		{	// destroy designated object and store new pointer
		if (_Ptr != _Myptr)
			delete _Myptr;
		_Myptr = _Ptr;
		}

以上是memory头文件中的release和reset源码。

先看release函数。定义一个指针来接受myptr,然后将myptr置空,最后return 临时指针。如果直接调用该方法而不去使用别的指针进行接受的话,就会引起内存泄漏。这个函数意在将调用该函数的智能指针的所有权转移,如 ptr = my_auto2.release();就是将my_auto2的所有权转给ptr。(这个代码只是伪代码,有助于理解release函数,实际上需要考虑赋值运算符重载的问题)。当然你也可以直接my_auto.release();而左边不用任何东西去接收,只不过这样就会导致内存泄漏。

再来看reset函数。它有一个参数,默认下是空。首先进行myptr和参数的比较。如果此时没有参数(即为默认的0)且此时myptr非空,就是真正的reset,将myptr delete掉,然后将myptr置空,完成了释放内存的操作。另一种情况就是有参数的情况,只要myptr和参数不相等,就直接delete myptr,然后把参数再赋给myptr。删除旧对象,保存了一个新的对象。

最后看一下赋值运算符重载的实现,是release和reset的组合。

_Myt& operator=(_Myt& _Right) _THROW0()
		{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
		}

a = b,其中b即为参数Right。首先将b release,b被置空,绑定的对象返回作为reset的参数,this指针调用reset即将this指针中的内容delete,然后接收release返回的对象,就完成了赋值运算符的模拟。

 

 

 

 

 

最后,附上auto_ptr的源码。

		// TEMPLATE CLASS auto_ptr
template
	class auto_ptr;

template
	struct auto_ptr_ref
		{	// proxy reference for auto_ptr copying
	explicit auto_ptr_ref(_Ty *_Right)
		: _Ref(_Right)
		{	// construct from generic pointer to auto_ptr ptr
		}

	_Ty *_Ref;	// generic pointer to auto_ptr ptr
	};

template
	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
		}

	auto_ptr(auto_ptr_ref<_Ty> _Right) _THROW0()
		{	// construct by assuming pointer from _Right auto_ptr_ref
		_Ty *_Ptr = _Right._Ref;
		_Right._Ref = 0;	// release old
		_Myptr = _Ptr;	// reset this
		}

	template
		operator auto_ptr<_Other>() _THROW0()
		{	// convert to compatible auto_ptr
		return (auto_ptr<_Other>(*this));
		}

	template
		operator auto_ptr_ref<_Other>() _THROW0()
		{	// convert to compatible auto_ptr_ref
		_Other *_Cvtptr = _Myptr;	// test implicit conversion
		auto_ptr_ref<_Other> _Ans(_Cvtptr);
		_Myptr = 0;	// pass ownership to auto_ptr_ref
		return (_Ans);
		}

	template
		_Myt& operator=(auto_ptr<_Other>& _Right) _THROW0()
		{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
		}

	template
		auto_ptr(auto_ptr<_Other>& _Right) _THROW0()
		: _Myptr(_Right.release())
		{	// construct by assuming pointer from _Right
		}

	_Myt& operator=(_Myt& _Right) _THROW0()
		{	// assign compatible _Right (assume pointer)
		reset(_Right.release());
		return (*this);
		}

	_Myt& operator=(auto_ptr_ref<_Ty> _Right) _THROW0()
		{	// assign compatible _Right._Ref (assume pointer)
		_Ty *_Ptr = _Right._Ref;
		_Right._Ref = 0;	// release old
		reset(_Ptr);	// set new
		return (*this);
		}

	~auto_ptr() _NOEXCEPT
		{	// destroy the object
		delete _Myptr;
		}

	_Ty& operator*() const _THROW0()
		{	// return designated value
 #if _ITERATOR_DEBUG_LEVEL == 2
		if (_Myptr == 0)
			_DEBUG_ERROR("auto_ptr not dereferencable");
 #endif /* _ITERATOR_DEBUG_LEVEL == 2 */

		return (*get());
		}

	_Ty *operator->() const _THROW0()
		{	// return pointer to class object
 #if _ITERATOR_DEBUG_LEVEL == 2
		if (_Myptr == 0)
			_DEBUG_ERROR("auto_ptr not dereferencable");
 #endif /* _ITERATOR_DEBUG_LEVEL == 2 */

		return (get());
		}

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

	_Ty *release() _THROW0()
		{	// return wrapped pointer and give up ownership
		_Ty *_Tmp = _Myptr;
		_Myptr = 0;
		return (_Tmp);
		}

	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
	};

 

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