指针,相信大家并不陌生。无论是我们在进行查看内存还是在修改字符串,我们都会用到指针。
最常见的情况则是我们使用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的基本功能,再来了解一下它的弊端。
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();
}
}
运行结果:
我们可以看到在绑定时输出Simple:1,之后也能正常实现Test类中的功能,同时my_auto可以通过get方法进行裸指针赋值以及使用*进行解引用操作,与普通指针无异。最后函数结束后,在调用析构函数的同时auto_ptr的析构函数也将Test类的对象delete掉,我加入的内存泄漏探测工具也显示No memory leaks detected(没有检查测到内存泄漏).
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(内部实现),所以调用函数当然会出错。
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函数参数改为引用传递就可以了。
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
};