C++智能指针之auto_ptr
auto_ptr
smart pointer with strict object ownership semantics 强调对对象的拥有权的智能指针
auto_ptr 在C++11中被弃用,已经在C++17中被移除,不过这实际上也是最简单的智能指针。我们先从auto_ptr的结构入手:
私有成员
只有一个私有成员_Myptr
用来存储指针。
工具函数
这些工具函数数比较简单,因此我们先从这里入手,再继续看构造函数等。
get()
用于获取存储的指针。
_Ty *get() const noexcept
{ // return wrapped pointer
return _Myptr;
}
reset()
将当前存储的指针重置为另一个
void reset(_Ty *_Ptr = nullptr)
{ // destroy designated object and store new pointer
if (_Ptr != _Myptr)
{
delete _Myptr;
}
_Myptr = _Ptr;
}
release()
释放当前指针的所有权(有点pop的味道)
_Ty *release() noexcept
{ // return wrapped pointer and give up ownership
_Ty *_Tmp = _Myptr;
_Myptr = nullptr;
return _Tmp;
}
一些重要的方法定义
构造函数
explicit auto_ptr(_Ty* _Ptr = nullptr) noexcept : _Myptr(_Ptr) {} // construct from object pointer
析构函数
~auto_ptr() noexcept {
delete _Myptr;
}
拷贝构造函数
auto_ptr(auto_ptr &_Right) noexcept : _Myptr(_Right.release())
{
// construct by assuming pointer from _Right auto_ptr
}
赋值构造函数
auto_ptr &operator=(auto_ptr &_Right) noexcept
{ // assign compatible _Right (assume pointer)
reset(_Right.release());
return *this;
}
下面看两个“取值”用到的运算符重载函数。auto_ptr的多线程访问存在问题,需要注意下。
operator*()
_Ty &operator*() const noexcept
{
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferencable");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return *get();
}
operator->()
_Ty *operator->() const noexcept
{
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferencable");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return get();
}
开始使用
到这里为止,auto_ptr似乎已经够用了。我们先来看看它的使用方法:
// auto_ptr_reset.cpp
// compile with: /EHsc
#include <memory>
#include <iostream>
#include <vector>
using namespace std;
class Int
{
public:
Int(int i)
{
x = i;
cout << "Constructing " << (void*)this << " Value: " << x << endl;
};
~Int()
{
cout << "Destructing " << (void*)this << " Value: " << x << endl;
};
int x;
};
int main()
{
auto_ptr<Int> pi(new Int(5)); // step 1
pi.reset(new Int(6)); // step 1.1
Int* pi2 = pi.get(); // step 2
Int* pi3 = pi.release(); //step 3
if (pi2 == pi3)
cout << "pi2 == pi3" << endl;
delete pi3;
}
更进一步(auto_ptr剩余部分)
在最开始提到了这样一句话:
smart pointer with strict object ownership semantics
auto_ptr
十分注重对一个对象的拥有权(ownership),这意味着你不能用来多个auto_ptr控制同一对象*。
这里所说的对象都是符合RAII思想的,举个例子,就像下面的使用例一样:
auto_ptr<int>(new int(10))
到目前为止,似乎auto_ptr似乎已经足够完善,不够仍然还有一些问题,我们先重点关注拷贝构造函数
auto_ptr(auto_ptr &_Right) noexcept : _Myptr(_Right.release()){}
这看起来与我们常见的拷贝构造函数不太一样。常见的长这样:T(const T &a);
而这里却是这样T(T & a)
。
首先看T(T & a)
,这里我们获得了另外一个auto_ptr的引用,并对其进行了清空操作以确保拥有权的转移。
不过,仔细观察,如果我们传入const auto_ptr &
类型的auto_ptr会有什么效果?显然,我们需要在release()中修改成员,这与const类型是相悖的。
从上面的例子中可以看出,可以通过将auto_ptr声明为const表示不希望移交拥有权。
到了这里你可能会想,只需要传入引用不就可以解决一切问题了吗?现在我们来观察下面两个例子
auto_ptr<int> ptr1(auto_ptr<int>(new int(1))); //使用临时对象进行拷贝构造
//另一种情况
auto_ptr<int> f(){
return auto_ptr<int>(new int(3)); //这里其实也使用临时对象进行拷贝构造
}
auto_ptr<int> ptr3(f()); //使用临时对象进行拷贝构造
这里的auto_ptr<int>(new int(1)
是一个临时对象,是一个典型的右值(const &),更通俗一点讲,是一个将亡值。而拷贝构造函数期望获得一个类型auto_ptr &
的参数。显然,对于一个将亡值取引用是没有意义的,因此编译器将抛出错误。
:point_down: 阅读有关右值引用的内容将帮助你进一步了解上面所提内容:point_up_2:
偏个题,能不能用下面的方法实现我们的需求?
explicit auto_ptr(_T *_Ptr = nullptr) : _Myptr(_Ptr) {}
explicit auto_ptr(auto_ptr &_Right) : _Myptr(_Right.release()) {}
explicit auto_ptr(auto_ptr _Right) throw() : _Myptr(_Right.release()) {} // Oops!
看起来最后的按值传递似乎能解决我们的问题,但不要忘了C++规定拷贝构造函数的参数类型必须为引用。这是因为按值传递传递的是参数的一份拷贝,那么使用拷贝构造函数则会陷入下面的逻辑循环:
调用拷贝构造函数->取得按值传递的参数的一份拷贝->调用参数的拷贝构造函数->...->无限循环
为了解决这个问题,这里我们先引入一个工具类auto_ptr_ref
来方便进行后续操作。
auto_ptr_ref
这是一个工具类,作为代理使用,具体见下文。
template <class _Ty>
struct auto_ptr_ref { // proxy reference for auto_ptr copying
explicit auto_ptr_ref(_Ty* _Right) : _Ref(_Right) {} // construct from generic pointer to auto_ptr ptr
_Ty* _Ref; // generic pointer to auto_ptr ptr
};
能看出我们想要做什么吗?我们想要把临时对象从右值变成一个“左值”以传递给auto_ptr,那么我们完全可以构造一个新的类将转换为“左值”并进行传递。
就像普通的class一样使用它(改成class也是完全可以的)
补充剩下的拷贝构造和赋值构造函数
auto_ptr(auto_ptr_ref<_Ty> _Right) noexcept
{ // construct by assuming pointer from _Right auto_ptr_ref
_Ty *_Ptr = _Right._Ref;
_Right._Ref = nullptr; // release old
_Myptr = _Ptr; // reset this
}
auto_ptr &operator=(auto_ptr_ref<_Ty> _Right) noexcept
{ // assign compatible _Right._Ref (assume pointer)
_Ty *_Ptr = _Right._Ref;
_Right._Ref = 0; // release old
reset(_Ptr); // set new
return *this;
}
让我们看一下效果:
总结下传递临时对象的匹配过程:
不同类型间的auto_ptr转换
这个比较简单,实现类似auto_ptr<char> p(auto_ptr<int>(new int(10)))
的效果
尖括号中所指的是原生指针所指向的类型,不是原生指针的类型
template <class _Other>
auto_ptr(auto_ptr<_Other> &_Right) noexcept : _Myptr(_Right.release())
{
// construct by assuming pointer from _Right
}
template <class _Other>
auto_ptr &operator=(auto_ptr<_Other> &_Right) noexcept
{ // assign compatible _Right (assume pointer)
reset(_Right.release());
return *this;
}
完整实现
这里不加修改地给出MSVC的实现
#if _HAS_AUTO_PTR_ETC
// CLASS TEMPLATE auto_ptr
template <class _Ty>
class auto_ptr;
template <class _Ty>
struct auto_ptr_ref { // proxy reference for auto_ptr copying
explicit auto_ptr_ref(_Ty* _Right) : _Ref(_Right) {} // construct from generic pointer to auto_ptr ptr
_Ty* _Ref; // generic pointer to auto_ptr ptr
};
template <class _Ty>
class auto_ptr { // wrap an object pointer to ensure destruction
public:
using element_type = _Ty;
explicit auto_ptr(_Ty* _Ptr = nullptr) noexcept : _Myptr(_Ptr) {} // construct from object pointer
auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {
// construct by assuming pointer from _Right auto_ptr
}
auto_ptr(auto_ptr_ref<_Ty> _Right) noexcept { // construct by assuming pointer from _Right auto_ptr_ref
_Ty* _Ptr = _Right._Ref;
_Right._Ref = nullptr; // release old
_Myptr = _Ptr; // reset this
}
template <class _Other>
operator auto_ptr<_Other>() noexcept { // convert to compatible auto_ptr
return auto_ptr<_Other>(*this);
}
template <class _Other>
operator auto_ptr_ref<_Other>() noexcept { // convert to compatible auto_ptr_ref
_Other* _Cvtptr = _Myptr; // test implicit conversion
auto_ptr_ref<_Other> _Ans(_Cvtptr);
_Myptr = nullptr; // pass ownership to auto_ptr_ref
return _Ans;
}
template <class _Other>
auto_ptr& operator=(auto_ptr<_Other>& _Right) noexcept { // assign compatible _Right (assume pointer)
reset(_Right.release());
return *this;
}
template <class _Other>
auto_ptr(auto_ptr<_Other>& _Right) noexcept : _Myptr(_Right.release()) {
// construct by assuming pointer from _Right
}
auto_ptr& operator=(auto_ptr& _Right) noexcept { // assign compatible _Right (assume pointer)
reset(_Right.release());
return *this;
}
auto_ptr& operator=(auto_ptr_ref<_Ty> _Right) noexcept { // assign compatible _Right._Ref (assume pointer)
_Ty* _Ptr = _Right._Ref;
_Right._Ref = 0; // release old
reset(_Ptr); // set new
return *this;
}
~auto_ptr() noexcept {
delete _Myptr;
}
_NODISCARD _Ty& operator*() const noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferencable");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return *get();
}
_NODISCARD _Ty* operator->() const noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
_STL_VERIFY(_Myptr, "auto_ptr not dereferencable");
#endif // _ITERATOR_DEBUG_LEVEL == 2
return get();
}
_NODISCARD _Ty* get() const noexcept { // return wrapped pointer
return _Myptr;
}
_Ty* release() noexcept { // return wrapped pointer and give up ownership
_Ty* _Tmp = _Myptr;
_Myptr = nullptr;
return _Tmp;
}
void reset(_Ty* _Ptr = nullptr) { // destroy designated object and store new pointer
if (_Ptr != _Myptr) {
delete _Myptr;
}
_Myptr = _Ptr;
}
private:
_Ty* _Myptr; // the wrapped object pointer
};
template <>
class auto_ptr<void> {
public:
using element_type = void;
};
#endif // _HAS_AUTO_PTR_ETC