C++智能指针auto_ptr详解

auto_ptr

auto_ptr是C++标准库中()为了解决资源泄漏的问题提供的一个智能指针类模板(注意:这只是一种简单的智能指针)

auto_ptr的实现原理其实就是RAII,在构造的时候获取资源,在析构的时候释放资源,并进行相关指针操作的重载,使用起来就像普通的指针。

std::auto_ptr<ClassA> pa(new ClassA);

但是由于其构造函数声明为explicit的,因此不能通过饮食转换来构造,只能显示调用构造函数

例如

auto_ptr<string> pstr(new string("abcd"));  // success
auto_ptr<string> pstr = new string("abcd"); // error

特性与使用技巧

下面主要分析一下auto_ptr的几个要注意的地方:

Transfer of Ownership

auto_ptr与boost库中的share_ptr不同的,auto_ptr没有考虑引用计数,因此一个对象只能由一个auto_ptr所拥有,在给其他auto_ptr赋值的时候,会转移这种拥有关系。

赋值,参数传递的时候会转移所有权

从上可知由于在赋值,参数传递的时候会转移所有权,因此不要轻易进行此类操作。

比如:
“`
std::auto_ptr pa(new ClassA());

bad_print(pa); //丢失了所有权

pa->…; //Error
“`

使用auto_ptr作为成员变量,以避免资源泄漏。

为了防止资源泄漏,我们通常在构造函数中申请,析构函数中释放,但是只有构造函数调用成功,析构函数才会被调用,换句话说,如果在构造函数中产生了异常,那么析构函数将不会调用,这样就会造成资源泄漏的隐患。

比如,如果该类有2个成员变量,指向两个资源,在构造函数中申请资源A成功,但申请资源B失败,则构造函数失败,那么析构函数不会被调用,那么资源A则泄漏。

为了解决这个问题,我们可以利用auto_ptr取代普通指针作为成员变量,这样首先调用成功的成员变量的构造函数肯定会调用其析构函数,那么就可以避免资源泄漏问题。

不要误用auto_ptr

  • auto_ptr不能共享所有权,即不要让两个auto_ptr指向同一个对象。

  • auto_ptr不能指向数组,因为auto_ptr在析构的时候只是调用delete,而数组应该要调用delete[]。

  • auto_ptr只是一种简单的智能指针,如有特殊需求,需要使用其他智能指针,比如share_ptr。

  • auto_ptr不能作为容器对象,STL容器中的元素经常要支持拷贝,赋值等操作,在这过程中auto_ptr会传递所有权,那么source与sink元素之间就不等价了。

auto_pstr的实现

参照STL-3.3源码memeory文件

STL的实现

成员变量_M_ptr

auto_ptr只有一个私有成员变量_M_ptr:

template <class _Tp> class auto_ptr {
private:
  _Tp* _M_ptr;

构造函数

其中 __STL_NOTHROW宏在stl_config.h文件中定义:

# define __STL_NOTHROW throw()

异常说明throw()在成员函数后面,这些成员函数将不抛出异常.

explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {}
  • 构造函数默认参数为NULL,在使用的时候可以不传参数.

  • 使用explicit,禁止参数的自动类型转换.

复制构造函数:

auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}

这里发现没有使用const 限制参数

这个auto_ptr的功能有关,在使用复制构造函数的时候,不仅复制出一个和原对象一样的对象,同时需要取消其控制权.

realease函数:

_Tp* release() __STL_NOTHROW {
  _Tp* __tmp = _M_ptr;
  _M_ptr = 0;
  return __tmp;
}

realease实现的功能就是将原智能指针的控制权取消.

get函数

返回智能指针指向对象的指针.

_Tp* get() const __STL_NOTHROW {
  return _M_ptr;
}

reset函数

void reset(_Tp* __p = 0) __STL_NOTHROW {
  if (__p != _M_ptr) {
    delete _M_ptr;
    _M_ptr = __p;
  }
}

reset函数提供了默认参数,功能是取消auto_ptr对原对象的控制权,并选择性提供一个新的对象控制权.

运算符重载=,*,->

其中=运算符,很关键,因为这个函数实现了智能指针的独占性

  auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW {
    if (&__a != this) {
      delete _M_ptr;
      _M_ptr = __a.release();  ////
    }
    return *this;
  }

  template <class _Tp1>
  auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW {
    if (__a.get() != this->get()) {
      delete _M_ptr;
      _M_ptr = __a.release();
    }
    return *this;
  }


  _Tp& operator*() const __STL_NOTHROW {
    return *_M_ptr;
  }
  _Tp* operator->() const __STL_NOTHROW {
    return _M_ptr;
  }

auto_ptr_ref引用对象

auto_ptr_ref的实现很简单,与auto_ptr中的几个成员函数相关,完成辅助功能.为什么需要提供auto_ptr_ref这样一个辅助结构呢?

前面说过auto_ptr功能主要是实现对对象控制权的安全控制,所在它的复制构造函数没有对参数进行const限制,因为需要修改.而这还涉及到标准C++左值右值的概念.

好吧,我们只讨论标准C++,后面会说明为什么这种实现的方式是为了标准C++考虑的.

lvalue && rvalue

关于左值 右值的具体分析与区分,网上有很多博客和帖子中都有说过.来看看比较权威的说明:

The names rvalue and lvalue come originally from the assignment expression expr1 = expr2, in which the left operand expr1 must be a (modifiable) lvalue (“left value”).

However, an lvalue is perhaps better considered as representing an object locator value. Thus, it is an expression that designates an object by name or address(pointer or reference). Lvalues need not be modifiable. For example, the name of a constant object is a nonmodifiable lvalue. All expressions that are not lvalues are rvalues. In particular, temporary objects created explicitly (T()) or as the result of a function call are rvalues.

     The C++ Standard Library by Nicolai M. Josuttis

An ordinary copy constructor can copy an rvalue, but to do so it must declare its parameter as a reference to a const object.

也就是说当给一个复制构造函数传递参数的时候,如果是右值,则构造函数参数必须是const reference.

而前面auto_ptr的复制构造函数却不是这样子的. 所以在实现的时候添加了auto_ptr_ref辅助来实现.实现的机制是通过auto_ptr_ref实现一个右值到中间左值的转化过程.

引用对象的定义如下

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

所以auto_ptr需要提供类类型转换,然后还需要提供重载的复制构造函数.下面就是这两个函数:

auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW
  : _M_ptr(__ref._M_ptr) {}

template <class _Tp1> operator auto_ptr_ref<_Tp1>() __STL_NOTHROW
  { return auto_ptr_ref<_Tp1>(this->release()); }

同样引入新增了=运算符重载和赋值构造函数

auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW
    : _M_ptr(__ref._M_ptr) {}

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

这也是为什么当看到源码的时候发现auto_ptr是通过两部分实现的.

曲线实现的方法确实是很精巧的

我自己的实现

下面是我自己实现的源码

#include <iostream>
#include <string>


#include <typeinfo>
using namespace std;

#if defined(__SGI_STL_USE_AutoPtr_CONVERSIONS) && \
    defined(__STL_MEMBER_TEMPLATES)

template<class U>
struct AutoPtrRef
{
  U* m_pointee;

  AutoPtrRef(U* p)
  :m_pointee(p)
  {

  }

};

#endif /* auto ptr conversions && member templates */

template<class T>
class AutoPtr
{
public :
    /// constructor
    explicit AutoPtr(T *p = NULL)
    :m_pointee(p)
    {
        /// NOP
    }

    /// copy constructor
    template<class U>
    AutoPtr(AutoPtr<U> &rhs)    // 由于要释放rhs指针的指向, 因此参数不能为const
    :m_pointee(rhs.release())
    {
        /// NOP
    }

    ///
    ~AutoPtr()
    {
        delete this->m_pointee;
    }

    ///
    /// 基本操作 获取get, release释放, 重置reset
    ///

    /// 获取指针的指向
    T* get( ) const
    {
        return m_pointee;
    }

    /// 释放指针的指向
    T* release( )
    {
        T *temp = this->m_pointee;  // 保存原指向地址
        this->m_pointee = NULL;     // 将指针指空

        /// 内存的释放由析构函数完成
        return temp;
    }

    /// 重置指针的指向
    void reset(T *p)
    {
        if(m_pointee != p)
        {
            delete m_pointee;       // 释放原来的空间
            this->m_pointee = p;    // 修改指向
        }
    }

    ///
    /// 重载操作符
    ///

    /// *ptr取地址
    T& operator*( ) const
    {
        if(m_pointee == NULL)
        {
            std::cout <<"pointee is NULL..." <<std::endl;
        }
        return *m_pointee;
// try
// {
// typeid(*m_pointee).name();
//
// return *m_pointee;
// }
// catch(std::bad_typeid)
// {
// std::cout <<__LINE__ <<std::endl;
// cout << "Object is NULL" << endl;
// }
    }

    /// 指针指向->
    T*  operator->( ) const
    {
        return m_pointee;
    }

    template<class U>
    AutoPtr& operator=(AutoPtr<U> &ptr)
    {
        /// auto_pstr独占性的关键
        if(this->m_pointee != ptr.m_pointee)
        {
            // 由于要修改左值的指向
            delete this->m_pointee;

            this->m_pointee = ptr.m_pointee;
            ptr.release( );                             // 释放右值指针的指向

        }
        return *this;
    }

    ///
    /// 增加的引用接口
    ///
#if defined(__SGI_STL_USE_AutoPtr_CONVERSIONS) && \
    defined(__STL_MEMBER_TEMPLATES)
    /// 构造函数
    AutoPtr(AutoPtrRef<T> ref)
    : m_pointee(ref.m_pointee)
    {

    }

    // 运算符
    AutoPtr& operator=(AutoPtrRef<T> ref)
    {
        if (ref.m_pointee != this->get()) {
        delete m_pointee;
      m_pointee = ref.m_pointee;
    }
    return *this;
  }

    template <class U>
    operator AutoPtrRef<U>( )
    {
        return AutoPtrRef<U>(this->release());
    }

    template <class U>
    operator AutoPtr<U>( )
    {
        return AutoPtr<U>(this->release());
    }

#endif /* auto ptr conversions && member templates */

private :
    T   *m_pointee;
};


int main(void)
{

    AutoPtr<string> pstr1(new string("jeancheng"));

    AutoPtr<string> pstr2(new string("gatieme"));


    std::cout <<*pstr1 <<std::endl;
    std::cout <<*pstr2 <<std::endl;

    pstr1 = pstr2;

    std::cout <<*pstr1 <<std::endl;
    std::cout <<*pstr2 <<std::endl;           // segment fault -=> becase


    return 0;


}

参照

auto_ptr、shared_ptr、weak_ptr、scoped_ptr用法小结

C++ Standard Stl – SGI STL源码学习笔记(01) auto_ptr

对比–Boost智能指针——shared_ptr

你可能感兴趣的:(C语言,指针,auto,PTR,智能)