首页 > 代码库 > auto_ptr源码剖析
auto_ptr源码剖析
auto_ptr是当前C++标准库(STL)中提供的一种智能指针,包含头文件 #include<memory> 便可以使用。auto_ptr 能够方便的管理单个堆内存对象,下面贴出SGI中的auto_ptr源码。
1 /* 2 * Copyright (c) 1997-1999 3 * Silicon Graphics Computer Systems, Inc. 4 * 5 * Permission to use, copy, modify, distribute and sell this software 6 * and its documentation for any purpose is hereby granted without fee, 7 * provided that the above copyright notice appear in all copies and 8 * that both that copyright notice and this permission notice appear 9 * in supporting documentation. Silicon Graphics makes no 10 * representations about the suitability of this software for any 11 * purpose. It is provided "as is" without express or implied warranty. 12 * 13 */ 14 15 #ifndef __SGI_STL_MEMORY 16 #define __SGI_STL_MEMORY 17 18 #include <stl_algobase.h> 19 #include <stl_alloc.h> 20 #include <stl_construct.h> 21 #include <stl_tempbuf.h> 22 #include <stl_uninitialized.h> 23 #include <stl_raw_storage_iter.h> 24 25 26 __STL_BEGIN_NAMESPACE 27 28 #if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && 29 defined(__STL_MEMBER_TEMPLATES) 30 31 template<class _Tp1> struct auto_ptr_ref { 32 _Tp1* _M_ptr; 33 auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {} 34 }; 35 36 #endif 37 38 template <class _Tp> class auto_ptr { 39 private: 40 _Tp* _M_ptr; 41 42 public: 43 typedef _Tp element_type; 44 45 explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {} 46 auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {} 47 48 #ifdef __STL_MEMBER_TEMPLATES 49 template <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW 50 : _M_ptr(__a.release()) {} 51 #endif /* __STL_MEMBER_TEMPLATES */ 52 53 auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW { 54 if (&__a != this) { 55 delete _M_ptr; 56 _M_ptr = __a.release(); 57 } 58 return *this; 59 } 60 61 #ifdef __STL_MEMBER_TEMPLATES 62 template <class _Tp1> 63 auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW { 64 if (__a.get() != this->get()) { 65 delete _M_ptr; 66 _M_ptr = __a.release(); 67 } 68 return *this; 69 } 70 #endif /* __STL_MEMBER_TEMPLATES */ 71 72 // Note: The C++ standard says there is supposed to be an empty throw 73 // specification here, but omitting it is standard conforming. Its 74 // presence can be detected only if _Tp::~_Tp() throws, but (17.4.3.6/2) 75 // this is prohibited. 76 ~auto_ptr() { delete _M_ptr; } 77 78 _Tp& operator*() const __STL_NOTHROW { 79 return *_M_ptr; 80 } 81 _Tp* operator->() const __STL_NOTHROW { 82 return _M_ptr; 83 } 84 _Tp* get() const __STL_NOTHROW { 85 return _M_ptr; 86 } 87 _Tp* release() __STL_NOTHROW { 88 _Tp* __tmp = _M_ptr; 89 _M_ptr = 0; 90 return __tmp; 91 } 92 void reset(_Tp* __p = 0) __STL_NOTHROW { 93 if (__p != _M_ptr) { 94 delete _M_ptr; 95 _M_ptr = __p; 96 } 97 } 98 99 // According to the C++ standard, these conversions are required. Most100 // present-day compilers, however, do not enforce that requirement---and, 101 // in fact, most present-day compilers do not support the language 102 // features that these conversions rely on.103 104 #if defined(__SGI_STL_USE_AUTO_PTR_CONVERSIONS) && 105 defined(__STL_MEMBER_TEMPLATES)106 107 public:108 auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW109 : _M_ptr(__ref._M_ptr) {}110 111 auto_ptr& operator=(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW {112 if (__ref._M_ptr != this->get()) {113 delete _M_ptr;114 _M_ptr = __ref._M_ptr;115 }116 return *this;117 }118 119 template <class _Tp1> operator auto_ptr_ref<_Tp1>() __STL_NOTHROW 120 { return auto_ptr_ref<_Tp1>(this->release()); }121 template <class _Tp1> operator auto_ptr<_Tp1>() __STL_NOTHROW122 { return auto_ptr<_Tp1>(this->release()); }123 124 #endif /* auto ptr conversions && member templates */125 };126 127 __STL_END_NAMESPACE128 129 #endif /* __SGI_STL_MEMORY */
相信对STL有了解的同学对auto_ptr不会陌生,使用起来很方便,目的是为了方便管理堆上创建的指针,防止内存泄露。这里只谈谈源码中一些值得注意的要点:
- auto_ptr构造函数中使用explicit,防止复制/拷贝时不必要的类型转换。
- auto_ptr析构函数将原生指针delete掉,这里可能出现重复释放的问题:View Code
1 int main()2 {3 int* p = new int(12);4 auto_ptr<int> aptr1(p);5 auto_ptr<int> aptr2(p);6 7 return 0;8 }
上述代码中由于aptr1和aptr2均指向同一块内存p,两个对象析构时会出现同一块内存重复释放的问题,所以应避免上述编码方式。
- auto_ptr析构函数调用操作符delete,而不是delete[],所以auto_ptr不能用于管理分配在堆上的数组指针。
- auto_ptr拷贝构造函数、赋值操作符参数声明为 auto_ptr& __a,而一般类中定义的拷贝构造函数、赋值操作符,参数类型为const T&。拷贝函数、赋值操作符中又调用了release()函数,注意release()函数会对auto_ptr中的原生指针重新赋值,在拷贝、赋值时,需要特别注意。View Code
1 int main()2 {3 int* p = new int(12);4 auto_ptr<int> aptr1(p);5 auto_ptr<int> aptr2(aptr1);6 cout<<*aptr1<<endl;7 8 return 0;9 }
上述代码中line6,输出*aptr1时,会出现undefine行为,因为在构造aptr2时,aptr1中原生指针已经被赋值为NULL。这种情况会更隐蔽的出现在函数调用传参或者返回时:
上述代码line11,输出*scope时,同样会出现undefine行为,因为在调用foo时,scope原生指针已经被赋值为NULL。auto_ptr的这种特点(缺点),导致容器不能很好的支持auto_ptr类。容器内元素的类型约束为元素类型必须支持赋值运算(引用不支持赋值,所以引用类型不能被放入容器),元素类型的对象必须可以复制(IO库类型不支持复制或赋值,所以IO类型对象不能放入容器),对于auto_ptr这种类型来说,它的赋值和复制已不是传统意义上的概念,所以放入容器中会出现很多未定义的后果。同样在作为函数入参时,除非用const & 进行修饰,否则也会出现未定义操作。View Code1 void foo(auto_ptr<int> ap) 2 { 3 cout<<*ap<<endl; 4 return; 5 } 6 7 int main() 8 { 9 auto_ptr<int> scope(new int(12));10 foo(scope);11 cout<<*scope<<endl;12 13 return 0;14 }
- auto_ptr拷贝构造函数、赋值操作符使用了成员函数模板,用途在于使auto_ptr实现继承体系的转换,查看如下代码:View Code
1 class Person 2 { 3 public: 4 virtual void action()const = 0; 5 }; 6 class Student : public Person 7 { 8 public: 9 virtual void action()const10 {11 cout<<"Student action"<<endl;12 }13 };14 class Teacher : public Person15 {16 public:17 virtual void action()const18 {19 cout<<"Teacher action"<<endl;20 }21 };22 23 void raw_action(const Person *p)24 {25 p->action();26 }27 28 void apt_action(auto_ptr<Person> apt)29 {30 apt->action();31 }32 33 int main()34 {35 Student * pStu = new Student;36 Teacher * pTea = new Teacher;37 raw_action(pStu);38 raw_action(pTea);39 40 auto_ptr<Student> apStu(pStu);41 auto_ptr<Teacher> apTea(pTea);42 43 //这里如果直接调用会出现二义性,有多个从auto_ptr<Derived>到auto_ptr<Base>的转换:构造函数和operator转换操作符44 //apt_action(apStu);45 //apt_action(apTea)46 auto_ptr<Person> ap1(apStu);47 auto_ptr<Person> ap2(apTea);48 49 apt_action(ap1);50 apt_action(ap2);51 52 return 0;53 }
代码中使用原生指针可以轻松实现多态,而对于auto_ptr,auto_ptr<Person>与auto_ptr<Student>和auto_ptr<Teacher>本不构成继承体系,由于有了成员模板函数才使得它们能够完成有派生类向基类的转换。
- auto_ptr中定义了一个struct auto_ptr_ref,它使我们可以拷贝和赋值临时的auto_ptr:View Code
1 auto_ptr<int> fun() 2 { 3 return auto_ptr<int>(new int(12)); 4 } 5 6 int main() 7 { 8 auto_ptr<int> ap1 = auto_ptr<int>(new int(12));//用一个临时变量进行赋值初始化 9 10 auto_ptr<int> ap2 (fun());//调用fun()返回一个临时auto_ptr进行初始化11 12 return 0;13 }
由于auto_ptr的控制复制函数入参类型都是非const的,而临时对象只能绑定在const &或者具体类型上,而对于第一句会调用auto_ptr的拷贝构造函数,但是拷贝构造函数入参是一个非const引用,无法将临时对象绑定在非const引用上,所以编译不会通过;对于fun()函数返回的auto_ptr对象,同样会调用拷贝构造,而此时的临时对象同样无法绑定在非const引用上。auto_ptr_ref的引入就是为了解决这个问题。具体来说,auto_ptr<int> ap1 = auto_ptr<int>(new int(12)); 首先创建了一个原生指针指向12的auto_ptr的临时对象,根据
进行转换,返回一个原生指针指向12的 auto_ptr_ref<int>的临时对象(右值),再根据View Code1 template <class _Tp1> operator auto_ptr_ref<_Tp1>() __STL_NOTHROW2 { return auto_ptr_ref<_Tp1>(this->release()); }
View Code1 auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW2 : _M_ptr(__ref._M_ptr) {}
此构造函数的入参时值类型 auto_ptr_ref<_Tp> __ref 不是引用,而是值传递,所以临时对象(右值)可以进行绑定,auto_ptr_ref<_Tp>先调用默认生成的拷贝构造函数,生成入参 __ref,在 _M_ptr(__ref._M_ptr)之后临时对象析构。
auto_ptr是C++标准中功能最简单的智能指针,主要为了解决资源泄漏的问题。它的实现原理其实是RAII,在构造的时候获取资源,在析构的时候释放资源,从源码中看出实现有很多巧妙之处。上面提到了一些auto_ptr的不足之处,可以使用更高级的带有引用计数的智能指针来代替。
auto_ptr源码剖析