STL之智能指针
来源:互联网 发布:网络迷情女人视频 编辑:程序博客网 时间:2024/05/21 06:59
在c++11里 auto_ptr 被声明为过时的。
智能指针在std_memory.h中实现,下面是它的源码(稍有改动):
template <class _Tp1>struct auto_ptr_ref{ _Tp1* _M_ptr; auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {}};template <class _Tp>class auto_ptr{private: _Tp *_M_ptr;public: typedef _Tp element_type; auto_ptr(_Tp *p = 0) throw() : _M_ptr(p) {} // 复制构造函数和赋值函数 auto_ptr(auto_ptr &a) throw() : _M_ptr(a.release()) {} auto_ptr &operator=(auto_ptr &__a) throw() { reset(__a.release()); return *this; } // 泛化复制和赋值 template <class _Tp1> auto_ptr(auto_ptr<_Tp1> &__a) throw() : _M_ptr(__a.release()) {} template <class _Tp1> auto_ptr &operator=(auto_ptr<_Tp1> &__a) throw() { reset(__a.release()); return *this; } // 类型转换运算符 template <class _Tp1> operator auto_ptr<_Tp1>() throw() { return auto_ptr<_Tp1>(this->release()); } ~auto_ptr() { delete _M_ptr; } _Tp &operator*() const throw() { return *_M_ptr; } _Tp *operator->() const throw() { return _M_ptr; } _Tp *get() const throw() { return _M_ptr; } // 将指针清零并返回清零前的指针 _Tp *release() throw() { _Tp *__tmp = _M_ptr; _M_ptr = 0; return __tmp; } // 将_M_ptr清零后指向新的内容 void reset(_Tp* __p = 0) throw() { if (__p != _M_ptr) { delete _M_ptr; _M_ptr = __p; } }// 以下存在意义在于接受右值参数, 详见于对应文档public: template <class _Tp1> operator auto_ptr_ref<_Tp1>() throw() { return auto_ptr_ref<_Tp>(this->release()); } auto_ptr(auto_ptr_ref<_Tp> __ref) throw() : _M_ptr(__ref._M_ptr) {} auto_ptr &operator=(auto_ptr_ref<_Tp> __ref) throw() { if (__ref._M_ptr != this->get()) { delete _M_ptr; _M_ptr = __ref._M_ptr; } return *this; }};
原理
auto_ptr的拷贝构造函数和赋值操作符,与一般类的做法不太相同,它们的参数都是auto_ptr&,而不是auto_ptr const &。一般来说,类的拷贝构造函数和赋值操作符的参数都是const &。但是auto_ptr的做法也是合理的:确保拥有权能够转移。
auto_ptr强调对资源的拥有权(ownership)。也就是说,auto_ptr是“它所指对象”的拥有者,一个对象只能属于一个拥有者。
为了保证auto_ptr的拥有权唯一,auto_ptr的拷贝构造函数和赋值操作符做了这样一件事情:移除另一个auto_ptr的拥有权。
如果auto_ptr的拷贝构造函数和赋值操作符的参数是auto_ptr const &,那么实参的拥有权将不能转移。因为转移拥有权需要修改auto_ptr的成员变量,而实参确是一个const对象,不允许修改。
问题
但是呢,这样会带来一个问题。
假象写出这样的代码:
#include <iostream>#include <memory>using namespace std;int main(int argc, char **argv) { auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); // 使用临时对象进行拷贝构造 auto_ptr<int> ptr2; ptr2 = (auto_ptr<int>(new int(1))); // 使用临时对象进行赋值}
假设没有定义auto_ptr_ref类及相关的函数,那么这段代码将不能通过编译。主要的原因是,拷贝构造函数及赋值操作符的参数:auto_ptr<int>(new int(1))
是临时对象。临时对象属于典型的右值,而非const &是不能指向右值的。(参见More Effective C++,Item 19)。比如:
int &a = 5; // 错误const int &b = 5; // 可以, 仅语法通过, 实际没什么意义, const int b = 5;效果是一样的
auto_ptr的拷贝构造函数及赋值操作符的参数类型恰恰是auto_ptr&,而非const auto_ptr &。
同理,下面的两段代码,也不会通过编译:
#include <iostream>#include <memory>using namespace std;auto_ptr<int> f() { return auto_ptr<int>(new int(1)); // 这里也使用临时对象进行拷贝构造}int main() { auto_ptr<int> ptr3(f()); // 使用临时对象进行拷贝构造 auto_ptr<int> ptr4; ptr4 = f(); // 使用临时对象进行赋值 }
普通类不会遇到这个问题,是因为他们的拷贝构造函数及赋值操作符(不管是用户定义还是编译器生成的版本),参数都是const &。
auto_ptr_ref之原理
下面的构造函数,是可以接受auto_ptr临时对象的。
auto_ptr(auto_ptr __a) throw() : _M_ptr(__a.release()) { }
但上述构造函数不能通过编译,因为它会循环调用自己(我没弄明白)。我们稍作修改:
auto_ptr(auto_ptr_ref<element_type> __ref) throw() // element_type就是auto_ptr的模板参数: _M_ptr(__ref._M_ptr) {}
该版本的构造函数,可以接受auto_ptr_ref的临时对象。如果auto_ptr可以隐式转换到auto_ptr_ref,那么我们就能够用auto_ptr临时对象来调用该构造函数。这个隐式转换不难实现:
template<typename _Tp1>operator auto_ptr_ref<_Tp1>() throw(){ return auto_ptr_ref<_Tp1>(this->release()); }
好像auto_ptr_ref赋予了auto_ptr“引用”的语义一样。
用mutable突破限制
问题的根源在于非const &是不能指向右值的,那么就让复制构造和赋值函数的形参变成const &。
因为形参auto_ptr const& a会调用release(),所以release()必须也变为const,但是release()还需要修改私有成员_M_ptr的值。此时,mutable修饰符出场,将_M_ptr限定为mutable的,问题解决。
实现如下(顺带测试了一下):
#include <iostream>template <typename T>class auto_ptr{private: mutable T *M_ptr;public: auto_ptr(T *p = 0) throw() : M_ptr(p) {} auto_ptr(auto_ptr const &a) throw() : M_ptr(a.release()) {} auto_ptr &operator=(auto_ptr const &a) throw() { reset(a.release()); return *this; } ~auto_ptr() { delete M_ptr; } T &operator*() const throw() { return *M_ptr; } T *operator->() const throw() { return M_ptr; } T *get() const throw() { return M_ptr; } // 唯有以下两个函数会对M_ptr进行修改 T *release() const throw() { T *tmp = M_ptr; M_ptr = 0; return tmp; } void reset(T *p = 0) throw() { if (p != M_ptr) { delete M_ptr; M_ptr = p; } } // 泛化复制和赋值 template <class T1> auto_ptr(auto_ptr<T1> &a) throw() : M_ptr(a.release()) {} template <class T1> auto_ptr &operator=(auto_ptr<T1> &a) throw() { reset(a.release()); return *this; } // 类型转换运算符 template <class T1> operator auto_ptr<T1>() throw() { return auto_ptr<T1>(this->release()); }};int main(){ auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); auto_ptr<int> ptr2; ptr2 = (auto_ptr<int>(new int(1))); std::cout << *ptr1 << *ptr2; return 0;}
需要注意的是,mutable T *M_ptr;
可以写成T mutable *M_ptr;
,但不能写成T * mutable M_ptr;
,虽然从意义上来说mutable修饰的确实是M_ptr。
mutable只能用于修饰类的非静态数据成员,它的存在就是为了突破const的限制,const auto_ptr的成员M_ptr是不可变的,但*M_ptr是可变的,既然mutable的功能只是为了突破const的限制,必然是让M_ptr变得可变。mutable的功能单一,放在语句的最前边也就行了。
虽然mutable和const是一对反义词,但从语法上来说,volatile和const比较像。
参考文献
为什么需要auto_ptr_ref
http://www.iteye.com/topic/746062
- STL之智能指针
- STL之智能指针
- STL之auto_ptr智能指针
- stl智能指针auto_ptr
- STL--智能指针(auto_ptr)
- STL 智能指针
- stl智能指针auto_ptr
- auto_ptr STL智能指针
- 【STL】智能指针auto_ptr
- 智能指针(STL)
- [STL] STL不支持智能指针
- 内存管理之智能指针一:stl中的auto_ptr智能指针
- 智能指针与STL容器
- c++ stl auto_ptr智能指针
- C++ STL 智能指针(二)
- stl智能指针和boost智能指针对比
- C++之智能指针
- 智能指针之auto_ptr
- Google maps API
- SQL 优化原则
- Unable to execute dex: Multiple dex files define Lorg/cocos2dx/lib/Cocos2dxAccelerometer
- BZOJ 1137 POI2009 Wsp 岛屿 半平面交
- Java IO 流读取文件
- STL之智能指针
- C++之STL中sort函数的内部实现(一)
- Android自动测试之monkey测试
- 使用STM32CubeMX开发三:按键中断实验
- uva 548
- [ios] xcode实用插件(持续更新)
- pycuda的安装和错误解决(含boost安装编译的详细过程)
- Using UrbanCode Deploy for Continuous Delivery of Dockerized Applications
- 队列在Java类库中的链式表示及实现——LinkedList