最简单的智能指针就是将指针封装在类里,同时将该类的复制与赋值禁用,也就是使该类失去值语义。
在boost中,这种智能指针也叫做scoped_ptr。
实现代码如下:
1 #ifndef SMART_PTR_H 2 #define SMART_PTR_H 3 #include <iostream> 4 5 6 template <typename T> 7 class SmartPtr 8 { 9 public: 10 typedef T value_type; 11 typedef T* pointer; 12 typedef T& reference; 13 14 SmartPtr(T *ptr = NULL) 15 :_ptr(ptr) 16 { } 17 18 ~SmartPtr() { delete _ptr; } 19 20 reference operator* () const throw() 21 { return *_ptr; } 22 23 pointer operator-> () const throw() 24 { return _ptr; } 25 26 private: 27 SmartPtr(const SmartPtr &); 28 void operator=(const SmartPtr &); 29 30 pointer _ptr; 31 }; 32 33 #endif /*SMART_PTR_H*/
相对于这种最简单的智能指针,我们还可以实现一种具有复制与赋值功能的智能指针——auto_ptr。
实现代码如下:
1 #ifndef AUTO_PTR_H 2 #define AUTO_PTR_H 3 #include <iostream> 4 5 template <typename T> 6 class AutoPtr 7 { 8 public: 9 typedef T value_type; 10 typedef T* pointer; 11 typedef T& reference; 12 13 14 AutoPtr(T *ptr = NULL); 15 AutoPtr(AutoPtr &other); 16 ~AutoPtr(); 17 AutoPtr &operator= (AutoPtr &other) throw(); 18 19 reference operator*() const throw() 20 { return *_ptr; } 21 22 pointer operator->() const throw() 23 { return _ptr; } 24 25 void reset(T *ptr = NULL) throw() 26 { 27 if(_ptr != ptr) 28 { 29 delete _ptr; 30 _ptr = ptr; 31 } 32 } 33 34 pointer release() throw() 35 { 36 pointer tmp(_ptr); 37 _ptr = NULL; 38 return tmp; 39 } 40 41 operator bool() throw() { return _ptr != NULL; } 42 43 private: 44 value_type *_ptr; 45 }; 46 47 48 template <typename T> 49 AutoPtr<T>::AutoPtr(T *ptr) 50 :_ptr(ptr) 51 { 52 53 } 54 55 template <typename T> 56 AutoPtr<T>::AutoPtr(AutoPtr<T> &other) 57 :_ptr(other._ptr) 58 { 59 other._ptr = NULL; 60 } 61 62 template <typename T> 63 AutoPtr<T>::~AutoPtr() 64 { 65 delete _ptr; 66 } 67 68 template <typename T> 69 AutoPtr<T> &AutoPtr<T>::operator= (AutoPtr<T> &other) throw() 70 { 71 reset(other.release()); 72 73 return *this; 74 } 75 #endif /*AUTO_PTR_H*/
在使用AutoPtr时,我们需要注意:
1.AutoPtr的复制与赋值会引发控制权的转移,这是一种转移语义。
2.不要使用AutoPtr,尤其是与容器结合。
3.在AutoPtr中,我们提供了一种转化,将该类可转化为bool型。
在C++11中,还有一种unique_ptr,该种智能指针也禁用了复制和赋值功能,但是它提供了移动构造能力和移动赋值 。
unique_ptr可以取代scoped_ptr,它保留了scoped_ptr的全部能力,同时提供了移动构造使得它可以放入容器中,没有带来额外的缺点。
unique_ptr简单实现代码如下:
1 #ifndef UNIQUE_PTR_H 2 #define UNIQUE_PTR_H 3 4 #include <algorithm> 5 #include <iostream> 6 7 template <typename T> 8 class UniquePtr 9 { 10 public: 11 typedef T value_type; 12 typedef T* pointer; 13 typedef T& reference; 14 15 explicit UniquePtr(T *ptr = NULL) throw() 16 :_ptr(ptr) 17 { 18 19 } 20 UniquePtr(UniquePtr &&s) throw() 21 :_ptr(s._ptr) 22 { 23 s._ptr = NULL; 24 } 25 UniquePtr &operator= (UniquePtr &&s) throw() 26 { 27 if(this != &s) 28 { 29 delete _ptr; 30 _ptr = s._ptr; 31 s._ptr = NULL; 32 } 33 34 return *this; 35 } 36 ~UniquePtr() throw() 37 { 38 delete _ptr; 39 } 40 41 reference operator* () const throw() 42 { return *_ptr; } 43 44 pointer operator-> () const throw() 45 { return _ptr; } 46 47 pointer get() const throw() 48 { return _ptr; } 49 50 pointer release() throw() 51 { 52 pointer tmp(_ptr); 53 _ptr = NULL; 54 return tmp; 55 } 56 57 void reset(T *ptr = NULL) throw() 58 { 59 if(_ptr != ptr) 60 { 61 delete _ptr; 62 _ptr = ptr; 63 } 64 } 65 66 67 void swap(UniquePtr &other) 68 { 69 std::swap(_ptr, other._ptr); 70 } 71 72 private: 73 UniquePtr(const UniquePtr &); 74 void operator= (const UniquePtr &); 75 76 pointer _ptr; 77 }; 78 79 80 81 82 83 #endif /*UNIQUE_PTR_H*/
在C++11中,我们可以使用unique_ptr为中介,将不具备复制、赋值和移动能力的元素放入容器中。
对于智能指针来说,比较常用的一种实现方法是使用引用计数,创建新的对象时,我们将计数器count初始化为1,每次复制该对象或将该对象赋值给其他对象时,计数器count加1,析构的时候将计数器减1,当计数器为0时,我们才真正删除该对象内的指针。
具体实现代码如下:
1 #ifndef COUNT_PTR_H 2 #define COUNT_PTR_H 3 #include <iostream> 4 5 6 template <typename T> 7 class CountPtr 8 { 9 public: 10 typedef T value_type; 11 typedef T* pointer; 12 typedef T& reference; 13 14 15 explicit CountPtr(T *p = NULL); 16 CountPtr(const CountPtr<T> &other); 17 ~CountPtr(); 18 19 CountPtr<T> &operator= (const CountPtr<T> &other); 20 reference operator*() const throw() { return *_ptr; } 21 22 pointer operator->() const throw() { return _ptr; } 23 24 size_t count() const throw() { return *_count; } 25 26 void swap(CountPtr<T> &other) throw() 27 { 28 std::swap(_ptr, other._ptr); 29 std::swap(_count, other._count); 30 } 31 32 void reset(T *ptr = NULL) throw() 33 { 34 dispose(); 35 36 _ptr = ptr; 37 _count = new size_t(1); 38 } 39 40 41 pointer get() const throw() { return _ptr; } 42 43 bool unique() const throw() { return *_count == 1; } 44 45 operator bool() { return _ptr != NULL; } 46 private: 47 48 void dispose() 49 { 50 if(-- *_count == 0) 51 { 52 delete _ptr; 53 delete _count; 54 } 55 } 56 57 T *_ptr; 58 size_t *_count; 59 60 }; 61 62 template <typename T> 63 CountPtr<T>::CountPtr(T *p) 64 :_ptr(p), 65 _count(new size_t(1)) 66 { 67 68 } 69 70 template <typename T> 71 CountPtr<T>::~CountPtr() 72 { 73 dispose(); 74 } 75 76 template <typename T> 77 CountPtr<T>::CountPtr(const CountPtr<T> &other) 78 :_ptr(other._ptr), 79 _count(other._count) 80 { 81 ++ (*_count); 82 } 83 84 template <typename T> 85 CountPtr<T> &CountPtr<T>::operator= (const CountPtr<T> &other) 86 { 87 ++(*other._count); //先++防止自身赋值失败 88 89 dispose(); 90 91 _ptr = other._ptr; 92 _count = other._count; 93 94 return *this; 95 } 96 #endif /*COUNT_PTR_H*/
在引用计数智能指针中,需要注意:
1.计数器count采用指针,这样每个指针共享一个计数。
2.析构时,仅将引用计数减1,只有当计数为0时,才释放资源。
3.复制指针时,计数将加1.
4.赋值时,可以先对other的计数加1,来避免处理自我赋值。
使用引用计数指针,可以将失去值语义的对象放入容器中。
在C++11中,提供了shared_ptr这种引用计数型的智能指针,但是需要注意:
shared_ptr使用不当会造成内存泄露,原因可能是两个堆上的对象相互引用,所以引用计数始终为1,造成一个环状结构,找不到对象delete的时机。
解决这种错误的方法是使用:weak_ptr.
weak_ptr要与shared_ptr配合使用,它只指向对象地址,但是不参与引用计数,是一种弱引用。
下面给出一种简单的内存泄露处理示例:
1 #include <iostream> 2 #include <memory> 3 using namespace std; 4 5 class Parent; 6 class Child; 7 8 typedef shared_ptr<Parent> ParentPtr; 9 typedef shared_ptr<Child> ChildPtr; 10 11 class Parent 12 { 13 public: 14 ~Parent() { cout << "~Parent" << endl; } 15 16 17 ChildPtr _child; 18 }; 19 20 class Child 21 { 22 public: 23 Child(const ParentPtr &ptr) 24 :_parent(ptr) 25 {} 26 ~Child() { cout << "~Child" << endl; } 27 28 //ParentPtr _parent; 29 std::weak_ptr<Parent> _parent; 30 }; 31 32 int main(int argc, const char *argv[]) 33 { 34 ParentPtr ptr1(new Parent); 35 ChildPtr ptr2(new Child(ptr1)); 36 ptr1->_child = ptr2; 37 ptr2->_parent = ptr1; 38 return 0; 39 }
在这里,我们将Child中的_parent指针设置为弱指针,当我们执行 ptr2->_parent = ptr1; 时,并没有将ptr1 中的计数加1,这是一种弱引用,当析构时,ptr1由于计数减 1 后计数为0,首先释放内存,这个时候,ptr1中的_child指针也被销毁,所以ptr2中的计数减1; 然后再析构ptr2时,将ptr2计数减 1 ,这时ptr2中的计数为0,释放内存。