C++智能指针auto_ptr

0x00 auto_ptr简介

auto_ptr 本身是 C++98 时代下提供的模板,在C++11及以上的环境下,不推荐使用。但是如果你的环境只支持C++98那么就只能使用它来智能的管理指针。

智能在使用时,跟一般指针一样。可以*-> 等等操作。

头文件:

#include <memory>

0x01 auto_ptr使用时的疑惑

首先写个简单的字符串类CStr用来测试auto_ptr

class CStr
{
public:
    CStr(const char* str = nullptr)
    {
        if (str != nullptr)
        {
            int nLen = strlen(str);
            m_pStr = new char[nLen + 1];
            strcpy_s(m_pStr, nLen + 1, str);
        }

    }
    CStr(const CStr& obj)
    {
        int nLen = strlen(obj.m_pStr);
        m_pStr = new char[nLen + 1];
        strcpy_s(m_pStr, nLen + 1, obj.m_pStr);
    }
    CStr(CStr&& obj)
    {
        std::swap(m_pStr, obj.m_pStr);
    }
    ~CStr()
    {
        if (m_pStr != nullptr)
        {
            delete m_pStr;
            m_pStr = nullptr;
        }
    }
    CStr& operator=(const CStr& obj)
    {
        // 深拷贝
        if (m_pStr != nullptr)
        {
            delete m_pStr;
            m_pStr = nullptr;
        }
        int nLen = strlen(obj.m_pStr);
        m_pStr = new char[nLen + 1];
        strcpy_s(m_pStr, nLen + 1, obj.m_pStr);
        return *this;
    }
    CStr& operator=(CStr&& obj)
    {
        // 浅拷贝
        std::swap(m_pStr, obj.m_pStr);
        return *this;
    }
    operator char* () // 转换运算符
    {
        return m_pStr == nullptr ? const_cast<char*>("NULL") : m_pStr;
    }
public:
    char* m_pStr;
};

然后使用auto_ptr

1.0 疑惑1

下面的代码运行会报错:

int main(int argc, char* argv[])
{

    CStr str1("string1");
    auto_ptr<CStr> pStr1(&str1);

    return 0;
}

1.1 疑惑2

我以为的auto_ptr被放弃的理由时这样的:

std::auto_ptr<std::string> pError(new std::string("Error"));
std::auto_ptr<std::string> pErrorTwo = pError;

一开始我以为auto_ptr没有写=运算符,导致=进行的是浅拷贝,然后两个智能指针出作用域析构的时候,会对同一个对象进行两次delete。

结果下面的代码会正常运行:

int main(int argc, char* argv[])
{
    auto_ptr<CStr> pStr2(new CStr("string2"));
    auto_ptr<CStr> pStr3 = pStr2;

    cout << *pStr3.get() << endl;
    return 0;
}

看了好多博客,发现想法都是和我差不多的,然后搞不懂原因我就读了一下auto_ptr的源码实现。

0x02 auto_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) {}

    auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}

    auto_ptr(auto_ptr_ref<_Ty> _Right) noexcept {
        _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 {
        reset(_Right.release());
        return *this;
    }

    template <class _Other>
    auto_ptr(auto_ptr<_Other>& _Right) noexcept : _Myptr(_Right.release()) {}
	/**********************************************/


    auto_ptr& operator=(auto_ptr& _Right) noexcept {
        reset(_Right.release());
        return *this;
    }

    auto_ptr& operator=(auto_ptr_ref<_Ty> _Right) noexcept {
        _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 dereferenceable");
#endif // _ITERATOR_DEBUG_LEVEL == 2

        return *get();
    }

    _NODISCARD _Ty* operator->() const noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
        _STL_VERIFY(_Myptr, "auto_ptr not dereferenceable");
#endif // _ITERATOR_DEBUG_LEVEL == 2

        return get();
    }

    _NODISCARD _Ty* get() const noexcept {
        return _Myptr;
    }

    _Ty* release() noexcept {
        _Ty* _Tmp = _Myptr;
        _Myptr    = nullptr;
        return _Tmp;
    }

    void reset(_Ty* _Ptr = nullptr) noexcept { // destroy designated object and store new pointer
        if (_Ptr != _Myptr) {
            delete _Myptr;
        }

        _Myptr = _Ptr;
    }

private:
    _Ty* _Myptr; // the wrapped object pointer
};

0x03 auto_ptr源码分析

3.0 私有数据

1.一个私有数据成员

仅有一个私有数据成员,类对象的指针:

private:
    _Ty* _Myptr; // the wrapped object pointer

3.2 公有成员

2.公有的成员函数

3.2.0 get()

提供对私有数据成员的访问方法:

_NODISCARD _Ty* get() const noexcept {
	return _Myptr;
}

3.2.1 重载*->

重载运算符*->分别用来取得对象的引用和对象的指针:

_NODISCARD _Ty& operator*() const noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
    _STL_VERIFY(_Myptr, "auto_ptr not dereferenceable");
#endif // _ITERATOR_DEBUG_LEVEL == 2

    return *get();
}

_NODISCARD _Ty* operator->() const noexcept {
#if _ITERATOR_DEBUG_LEVEL == 2
    _STL_VERIFY(_Myptr, "auto_ptr not dereferenceable");
#endif // _ITERATOR_DEBUG_LEVEL == 2

    return get();
}

3.2.2 release()

提供了释放类对象指针的方法:仅仅是将类对象指针置空,并返回原来的类对象指针。

_Ty* release() noexcept {
	_Ty* _Tmp = _Myptr;
	_Myptr    = nullptr;
	return _Tmp;
}

3.2.3 reset()

销毁指定对象并存储新的指针:

void reset(_Ty* _Ptr = nullptr) noexcept { // destroy designated object and store new pointer
    if (_Ptr != _Myptr) {
        delete _Myptr;
    }

    _Myptr = _Ptr;
}

3.2.4 重载=运算符

总体思路就是将=右边的auto_ptr对象中的指针置空,然后=左边的auto_ptr对象中保存=右边auto_ptr对象中的指针。如果=左边的auto_ptr对象中原来保存过指针,则会先释放这个指针。

假设=左边的auto_ptr对象为obj1=右边的auto_ptr对象为obj2

obj2调用release方法:将obj2保存的指针置空,并返回obj2原来保存的指针。
obj1调用reset方法:如果obj2原来保存的指针和obj1保存的指针不同,则obj1会先释放自己保存的指针,然后再保存obj2返回的指针。如果obj2原来保存的指针和obj1保存的指针相同,则不做处理。

auto_ptr& operator=(auto_ptr& _Right) noexcept {
    reset(_Right.release());
    return *this;
}
auto_ptr& operator=(auto_ptr_ref<_Ty> _Right) noexcept {
    _Ty* _Ptr = _Right._Ref;
    _Right._Ref = 0; // release old
    reset(_Ptr); // set new
    return *this;
}

3.2.5 构造函数

explicit auto_ptr(_Ty* _Ptr = nullptr) noexcept : _Myptr(_Ptr) {}

auto_ptr(auto_ptr& _Right) noexcept : _Myptr(_Right.release()) {}

auto_ptr(auto_ptr_ref<_Ty> _Right) noexcept {
    _Ty* _Ptr = _Right._Ref;
    _Right._Ref = nullptr; // release old
    _Myptr = _Ptr; // reset this
}

构造函数的参数可知,要么不初始化,要么传一个有效的合法值:

auto_ptr<CStr> pstr1; // OK

auto_ptr<CStr> pStr2(new CStr("string2")); // OK

3.2.6 析构函数

析构函数直接delete类对象指针:

~auto_ptr() noexcept {
	delete _Myptr;
}

C++的new和delete是配合使用的,new在堆中申请内存空间,然后调用对象的构造函数,delete调用对象的析构函数,然后释放在堆中申请的内存空间。

析构函数中,直接delete对象指针,也就说明这个对象指针_Myptr指向堆空间,也就是说这个对象是new申请的。

因此下面的代码一定会产生异常:看 4.0 解惑1

int main(int argc, char* argv[])
{
    CStr str1("string1");
    auto_ptr<CStr> pStr1(&str1);
    
    return 0;
}

0x04 解惑

4.0 解惑1

int main(int argc, char* argv[])
{

    CStr str1("string1");
    auto_ptr<CStr> pStr1(&str1);

    return 0;
}

首先看一下auto_ptr的析构函数。

str1对象是在栈空间中,初始化pStr1时没有任何问题,pStr1会保存str1对象的指针。然后pStr1析构,直接delete保存的str1对象的指针,delete时调用str1对象的析构函数,然后释放str1对象的空间,实际上str1对象这块内存在栈空间中,不在堆空间中,因此会发生异常。

4.1 解惑2

int main(int argc, char* argv[])
{
    auto_ptr<CStr> pStr2(new CStr("string2"));
    auto_ptr<CStr> pStr3 = pStr2;

    cout << *pStr3.get() << endl;
    return 0;
}

查看auto_ptr=运算符重载的源码可知,pStr2对象会先调用release方法,将保存的CStr对象的指针返回给pStr3,然后pStr2对象中的指针置空。
所以最后pStr3中保存对象的指针,而pStr2中的指针为空。在析构时,pStr3先析构,析构时没有任何问题。然后pStr2析构时,由于保存的指针为空,本来应该有问题,但是再次查看auto_ptr的析构函数,析构函数中使用关键字noexcept不抛出异常。

0x05 auto_ptr总结

1.由auto_ptr的析构函数可知,初始化auto_ptr时,必须用堆空间中的指针值初始化。

2.由auto_ptr=的源码可知,auto_ptr对象可以赋值,没有问题

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shlyyy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值