STL之智能指针
2015-05-20 11:17
309 查看
在c++11里 auto_ptr 被声明为过时的。
智能指针在std_memory.h中实现,下面是它的源码(稍有改动):
auto_ptr强调对资源的拥有权(ownership)。也就是说,auto_ptr是“它所指对象”的拥有者,一个对象只能属于一个拥有者。
为了保证auto_ptr的拥有权唯一,auto_ptr的拷贝构造函数和赋值操作符做了这样一件事情:移除另一个auto_ptr的拥有权。
如果auto_ptr的拷贝构造函数和赋值操作符的参数是auto_ptr const &,那么实参的拥有权将不能转移。因为转移拥有权需要修改auto_ptr的成员变量,而实参确是一个const对象,不允许修改。
假象写出这样的代码:
假设没有定义auto_ptr_ref类及相关的函数,那么这段代码将不能通过编译。主要的原因是,拷贝构造函数及赋值操作符的参数:
auto_ptr的拷贝构造函数及赋值操作符的参数类型恰恰是auto_ptr&,而非const auto_ptr &。
同理,下面的两段代码,也不会通过编译:
普通类不会遇到这个问题,是因为他们的拷贝构造函数及赋值操作符(不管是用户定义还是编译器生成的版本),参数都是const &。
但上述构造函数不能通过编译,因为它会循环调用自己(我没弄明白)。我们稍作修改:
该版本的构造函数,可以接受auto_ptr_ref的临时对象。如果auto_ptr可以隐式转换到auto_ptr_ref,那么我们就能够用auto_ptr临时对象来调用该构造函数。这个隐式转换不难实现:
好像auto_ptr_ref赋予了auto_ptr“引用”的语义一样。
因为形参auto_ptr const& a会调用release(),所以release()必须也变为const,但是release()还需要修改私有成员_M_ptr的值。此时,mutable修饰符出场,将_M_ptr限定为mutable的,问题解决。
实现如下(顺带测试了一下):
需要注意的是,
mutable只能用于修饰类的非静态数据成员,它的存在就是为了突破const的限制,const auto_ptr的成员M_ptr是不可变的,但*M_ptr是可变的,既然mutable的功能只是为了突破const的限制,必然是让M_ptr变得可变。mutable的功能单一,放在语句的最前边也就行了。
虽然mutable和const是一对反义词,但从语法上来说,volatile和const比较像。
http://www.iteye.com/topic/746062
智能指针在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_refhttp://www.iteye.com/topic/746062
相关文章推荐
- [C++][STL]智能指针:auto_ptr
- stl智能指针auto_ptr
- 【STL学习】智能指针之weak_ptr
- 智能指针容器及STL容器
- stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结
- [置顶] STL中的智能指针(Smart Pointer)及其源码剖析: std::auto_ptr
- [置顶] STL中的智能指针(Smart Pointer)及其源码剖析: std::unique_ptr
- auto_ptr智能指针不能作为STL标准容器的元素
- STL+auto_ptr智能指针简单分析
- STL 智能指针
- stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四种智能指针使用总结
- C++ (STL BOOST) 智能指针的一些解析
- ATL中对智能指针在STL中的错误处理进行了一层CAdapt模板类封装
- 为什么auto_ptr智能指针不能作为STL标准容器的元素
- 一个可以放入STL容器的简洁、高效、非计数的智能指针实现
- stl中使用智能指针的问题
- 智能指针与STL容器
- STL向导阅读②----智能指针
- 为什么auto_ptr智能指针不能作为STL标准容器的元素
- STL中的auto_ptr智能指针用法(转)