C++智能指针auto_ptr详解

简介:

auto_ptr


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

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

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

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

例如

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

特性与使用技巧


下面主要分析一下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;
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

构造函数


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

#   define __STL_NOTHROW throw()
  • 1
  • 1

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

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

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

复制构造函数:


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

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

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

realease函数:


_Tp* release() __STL_NOTHROW {
  _Tp* __tmp = _M_ptr;
  _M_ptr = 0;
  return __tmp;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

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

get函数


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

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

  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

reset函数


void reset(_Tp* __p = 0) __STL_NOTHROW {
  if (__p != _M_ptr) {
    delete _M_ptr;
    _M_ptr = __p;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

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) {}
};
  • 1
  • 2
  • 3
  • 4
  • 1
  • 2
  • 3
  • 4

所以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()); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

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

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;
  }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这也是为什么当看到源码的时候发现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;


}


转载:http://blog.csdn.net/gatieme/article/details/50939155

目录
相关文章
|
1月前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
83 0
|
2月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
199 4
|
3月前
|
存储 安全 编译器
在 C++中,引用和指针的区别
在C++中,引用和指针都是用于间接访问对象的工具,但它们有显著区别。引用是对象的别名,必须在定义时初始化且不可重新绑定;指针是一个变量,可以指向不同对象,也可为空。引用更安全,指针更灵活。
|
3月前
|
存储 C++
c++的指针完整教程
本文提供了一个全面的C++指针教程,包括指针的声明与初始化、访问指针指向的值、指针运算、指针与函数的关系、动态内存分配,以及不同类型指针(如一级指针、二级指针、整型指针、字符指针、数组指针、函数指针、成员指针、void指针)的介绍,还提到了不同位数机器上指针大小的差异。
101 1
|
3月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
60 2
|
3月前
|
存储 安全 编译器
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(一)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
3月前
|
存储 C++ 索引
C++函数指针详解
【10月更文挑战第3天】本文介绍了C++中的函数指针概念、定义与应用。函数指针是一种指向函数的特殊指针,其类型取决于函数的返回值与参数类型。定义函数指针需指定返回类型和参数列表,如 `int (*funcPtr)(int, int);`。通过赋值函数名给指针,即可调用该函数,支持两种调用格式:`(*funcPtr)(参数)` 和 `funcPtr(参数)`。函数指针还可作为参数传递给其他函数,增强程序灵活性。此外,也可创建函数指针数组,存储多个函数指针。
113 6
|
4月前
|
编译器 C++
【C++核心】指针和引用案例详解
这篇文章详细讲解了C++中指针和引用的概念、使用场景和操作技巧,包括指针的定义、指针与数组、指针与函数的关系,以及引用的基本使用、注意事项和作为函数参数和返回值的用法。
68 3
|
3月前
|
存储 编译器 程序员
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值(二)
【C++】C++特性揭秘:引用与内联函数 | auto关键字与for循环 | 指针空值
|
4月前
|
C++
C++(十八)Smart Pointer 智能指针简介
智能指针是C++中用于管理动态分配内存的一种机制,通过自动释放不再使用的内存来防止内存泄漏。`auto_ptr`是早期的一种实现,但已被`shared_ptr`和`weak_ptr`取代。这些智能指针基于RAII(Resource Acquisition Is Initialization)原则,即资源获取即初始化。RAII确保对象在其生命周期结束时自动释放资源。通过重载`*`和`-&gt;`运算符,可以方便地访问和操作智能指针所指向的对象。