std::auto_ptr源码分析

背景介绍

作为一个C/C++程序员经常在使用malloc或者new在堆上分配内存,然后很可能忘记使用free/delete将分配的内存释放掉,归还给操作系统。虽然大家都知道malloc/free、new/delete要成对使用,但是在多种场景可能忘记这个规则,比如说在某个功能函数中,在函数的开始部分使用malloc/new分配了内存,但是复杂的逻辑条件下,由于某些条件不满足,代码不能继续向下执行,而是直接返回,往往在返回之前忘记将分配的内存释放掉;再比如说,函数在执行的过程中发生了异常,直接退出了,这时候你想释放内存都不行了,你或许会说捕获异常,然后再在异常处理中将内存释放掉,虽然这种方法是可行的,当时如果异常类型且函数本身逻辑复杂也计较复杂的情况下,稍不注意,你很可能造成内存泄漏。
为了解决上述所说的内存泄漏问题,智能指针变应用而生的。其实智能指针的用途就是远不止这些,但是它扮演了一个相同的角色,就是资源管理者。

原理分析

上面讲到智能指针是一位资源管理者,它以对象的方式管理资源,并在资源获得的时候,将获得的资源放进智能指针管理对象中,在管理对象析构的时候才将先前放入的资源释放掉。这就是智能指针管理资源的原理。可将这个原理简单概括为:
1. 获得资源后立刻放入管理对象内。
2. 管理对象应用析构函数确保资源被释放掉。

源码分析

1、成员数据分析

成员数据非常简单,就只有唯一一个成员数据,就是_M_ptr,其类型为模板的typename类型_Tp,在实例化类auto_ptr的时候变确定_M_ptr所指的数据类型。此时再给其取了一个别名element_type,其详细定义如下:

template<typename _Tp>
class auto_ptr {
private:
    _Tp* _M_ptr;
public:
    typedef _Tp element_type;

2、成员函数分析

首先看一下类auto_ptr的成员函数release()和reset()。

element_type* release() throw() {
    element_type* __tmp = _M_ptr;
    _M_ptr = 0; //等效为_M_ptr = NULL
    return __tmp;
}

void reset(element_type* __p = 0) throw() {
    if (__p != _M_ptr) {
        delete _M_ptr;
        _M_ptr = __p;
    }
}

release()函数的功能就是获取对象数据成员_M_ptr的值,并原来的值赋空;
reset()函数的功能就是释放对象数据成员_M_ptr所指的内存,然后再让其指向新的auto_ptr类型地址__p。
其次,再来看一下下面这3种构造函数:

explicit auto_ptr(element_type* __p = 0) throw() : _M_ptr(__p) 
{ 
}
auto_ptr(auto_ptr& __a) throw() : _M_ptr(__a.release()) 
{ 
}
template<typename _Tp1>
auto_ptr(auto_ptr<_Tp1>& __a) throw() : _M_ptr(__a.release()) 
{ 
}

第一种构造函数,直接将new出来的_Tp类型指针放入到成员变量_M_ptr中;
第二种构造函数,将智能指针auto_ptr类型对象的成员变量值,赋给新的智能指针对象的成员变量,并将原先的auto_ptr类型对象的成员变量设置为空;
第三种构造函数,属于模板函数。将通过模板方式构造出的对象的成员变量_M_ptr的值赋给新构建的auto_ptr对象,同时将原先对象的成员变量_M_ptr设置空;
可将第二种和第三种构造函数都将传入构函数函数对象的成员变量_M_ptr都设置为空,传入的构造函数的对象再调用任何可掉用的方法都将获得默认的,无意义的数据。
然后,再来分析一下拷贝赋值运算函数,首先__a.release()操作就是将对象__a的成员变量_M_ptr的值返回,此时成员变量_M_ptr被设置为空。其次,就是判断被赋值对象的成员变量_M_ptr的值与__a.release()获得的值是否相等。如果不等,就把原先的_M_ptr的值所指的内存给释放掉,然后再赋予新值__a.release()。

auto_ptr& operator=(auto_ptr& __a) throw() {
    reset(__a.release());
    return *this;
}
template<typename _Tp1>
auto_ptr& operator=(auto_ptr<_Tp1>& __a) throw() {
    reset(__a.release());
    return *this;
}

拷贝赋值函数的功能就是将赋值对象的成员变量_M_ptr赋予给被赋值对象的成员变量_M_ptr,值得注意的是,此处赋值方式与普通的赋值方式不一样,其更像赠与方式,因为本身被清理掉了。
再然后,分析一下析构函数,其很简单,就是释放成员变量所指的内存。代码如下:

~auto_ptr() { delete _M_ptr; }

最后,再分析一下,下面这3个成员函数,其中*操作符成员函数的作用是获取成员变量_M_ptr所指的内容,->操作符成员函数的作用是获取取成员变量_M_ptr的值,get方法的作用也是获取成员变量_M_ptr的值。后面2个虽然功能相同,但是应用的对象不一样,前面一个应用于指针对象,后面一个应用于普通实例对象。

element_type& operator*() const throw() {
    __glibcxx_assert(_M_ptr != 0);
    return *_M_ptr; 
}
element_type* operator->() const throw() {
    __glibcxx_assert(_M_ptr != 0);
    return _M_ptr; 
}
element_type* get() const throw() { return _M_ptr; }

对于简单的应用,上面这些成员函数就可以满足要求,但是对于复杂的应用,其就变得无能为力了,比如说类型转型(指向派生类的智能指针转为指向基类的智能指针)。的确,这种需求场合还是很常见的。下面开始讲解智能指针提供的结构和模板方法。结构auto_ptr_ref 的具体定义如下:

template<typename _Tp1>
struct auto_ptr_ref {
    _Tp1* _M_ptr;
    explicit auto_ptr_ref(_Tp1* __p): _M_ptr(__p) { }
}

可以发现结构auto_ptr_ref,与智能指针auto_ptr本身非常相似,只不过它用了struct 关键字而已(此处可以class代替)。下面是其构造函数定义:

auto_ptr(auto_ptr_ref<element_type> __ref) throw(): _M_ptr(__ref._M_ptr) { }    

该构造函数的参数是没有使用引用,致使_M_ptr所指的数据是传入参数的拷贝。
下面看看一下类型转换操作符operator (),它们的具体定义如下:

template<typename _Tp1>
operator auto_ptr_ref<_Tp1>() throw() { return auto_ptr_ref<_Tp1>(this->release()); }
template<typename _Tp1>
operator auto_ptr<_Tp1>() throw() { return auto_ptr<_Tp1>(this->release());}

同时还提供了包含类型转换的第三个拷贝赋值函数,其功能就是在智能指针auto_ptr对象的数据成员与auto_ptr_ref不等的情况下,删除智能指针auto_ptr对象的数据成员所指的内存,然后再让其指向__ref的_M_ptr所指的对象。此处与前面的拷贝赋值函数不一样,因为__ref的_M_ptr所指的对象扔保持不变,不存在赠与,此处是共享。

auto_ptr& operator=(auto_ptr_ref<element_type> __ref) throw(){
    if (__ref._M_ptr != this->get())  {
        delete _M_ptr;
         _M_ptr = __ref._M_ptr;
    }
    return *this;
}

3、应用例子

在下面的例子中,假设Person为基类,BigMan为继承类。

string str("xiao ming");
std::auto_ptr<Person> ptr1(new Person(str, 12));                 (1)
std::auto_ptr<Person> ptr2 = ptr1;                               (2)
std::auto_ptr<BigMan> ptr3(new BigMan(str, 35));                 (3)
std::auto_ptr<Person> ptr4 = (std::auto_ptr<Person>)ptr2;        (4)

对于例子(1)来说,它会直接调用第一种构造函数;
同样,对于例子(2)来说,其会调用第二种构造函数;
当然,例子(3)跟例子(1)是相同的;
但是对于例子(4)来说,其稍微复杂些,下面是其先后调用的函数:

auto_ptr(auto_ptr<BigMan>& __a) throw() : _M_ptr(__a.release()) { }
operator auto_ptr_ref<Person>() throw() { 
    return auto_ptr_ref<Person>(this->release()); 
}
auto_ptr_ref(Person* __p): _M_ptr(__p) { }
auto_ptr(auto_ptr_ref<Person> __ref) throw() : _M_ptr(__ref._M_ptr) { }

可以发现,由BigMan类智能指针依靠结构auto_ptr_ref,才迂回转为Person智能指针。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值