IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++智能指针auto_ptr -> 正文阅读

[C++知识库]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对象可以赋值,没有问题

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-03-10 22:13:22  更:2022-03-10 22:17:18 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:41:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码