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