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++学习记录 - 智能指针 -> 正文阅读

[C++知识库]C++学习记录 - 智能指针

作者:recommend-item-box type_download clearfix

1. 什么是智能指针

简而言之,为了更安全的使用指针。
实现方式简单来说,就是用一个模板类把一般的指针包装起来。用这个类来维护内部指针的释放操作。
std中有四种智能指针。

  • auto_ptr(已弃用)
  • unique_ptr
  • shared_ptr
  • weak_ptr

2. auto_ptr

比较简单的智能指针,实现逻辑如下代码。
实现的原则是:内存空间只能由一个指针所拥有。
存在的问题:拷贝和赋值后,原对象内部的指针会为空,有风险。

template<class T>
class autoPtr{

public:
    autoPtr(T *a):m_ptr(a){

    }


    ~autoPtr(){
        if(m_ptr){
            delete m_ptr;
            m_ptr = nullptr;
        }

    }

    // 拷贝构造 权限转移
    autoPtr(autoPtr<T> &a):m_ptr(a.m_ptr) {
        a.m_ptr = nullptr;  
    }

    // 赋值 权限转移
    autoPtr& operator= (autoPtr<T> &a){
        if(this != &a){
            if(m_ptr) delete m_ptr;
            m_ptr = a.m_ptr;
            a.m_ptr = nullptr;
            
        }

        return *this;
    }
    
    T* operator->(){
        return m_ptr;
    }

    T& operator* (){
        return *m_ptr;
    }
    

private:
    T *m_ptr;

};

3. unique_ptr

是对auto_ptr的改进,实现逻辑如下代码。
实现的原则是:内存空间只能由一个指针所拥有,但是禁止了拷贝和赋值。

// uniquePtr  对于autoPtr来说 禁止了拷贝和赋值
template<class T>
class uniquePtr{
public:
    
    uniquePtr(const T*a):m_ptr(a) { };

    ~uniquePtr(){ if(m_ptr) delete m_ptr; }

    T& operator *(){
        return *m_ptr;
    }

    T* operator->(){
        return m_ptr;
    }

    T* get(){ return m_ptr; }

private:
    T *m_ptr;

    // 禁止拷贝和赋值
    uniquePtr(const uniquePtr<T> &a) { };

    uniquePtr<T> & operator = (const uniquePtr<T> &a) { };

};

4. shared_ptr

实现逻辑如下代码。
实现的原则是:内存空间可以由多个指针所拥有,但是要控制内存释放的时机,这个时机由“引用计数”来实现。
存在为问题:会存在循环引用问题,导致内存无法正常释放。

template<class T>
class sharedPtr{

public:

    sharedPtr(T *a):m_ptr(a), m_count(new int(1)) { };

    ~sharedPtr(){
        
        if(--(*m_count) == 0){
            delete m_ptr;
            delete m_count;
            m_ptr = nullptr;
            m_count = nullptr;

        }

    }


    T& operator *(){
        return *m_ptr;
    }

    T* operator->(){
        return m_ptr;
    }

    // 拷贝构造
    sharedPtr(const sharedPtr<T> &a):m_ptr(a.m_ptr),m_count(a.m_count) {
        ++(*m_count);
    }

    // 赋值函数
    sharedPtr<T> & operator=(const sharedPtr<T> &a){
        
        if(m_ptr != a.m_ptr){
            // this 原来的那个区域处理
            if(--(*m_count) == 0){   // 这里不仅仅是检查,也会引用计数减1
                delete m_ptr;
                delete m_count;
                m_ptr = nullptr;
                m_count = nullptr;
            }

            m_ptr = a.m_ptr;
            m_count = a.m_count;

            ++(*m_count);
        }

        return *this;
        
    }

    int use_count(){
        return *m_count;
    }

    int * use_countPtr(){
        return m_count;
    }

    T* get(){
        return m_ptr;
    }
    


private:
    T *m_ptr;
    int *m_count;

};

shared_ptr会产生循环引用问题,如下代码

template<class T>
class sharedPtr{

public:

    sharedPtr(T *a):m_ptr(a), m_count(new int(1)) { };

    ~sharedPtr(){
        
        if(--(*m_count) == 0){
            delete m_ptr;
            delete m_count;
            m_ptr = nullptr;
            m_count = nullptr;

        }

    }


    T& operator *(){
        return *m_ptr;
    }

    T* operator->(){
        return m_ptr;
    }

    // 拷贝构造
    sharedPtr(const sharedPtr<T> &a):m_ptr(a.m_ptr),m_count(a.m_count) {
        ++(*m_count);
    }

    // 赋值函数
    sharedPtr<T> & operator=(const sharedPtr<T> &a){
        
        if(m_ptr != a.m_ptr){
            // this 原来的那个区域处理
            if(--(*m_count) == 0){   // 这里不仅仅是检查,也会引用计数减1
                delete m_ptr;
                delete m_count;
                m_ptr = nullptr;
                m_count = nullptr;
            }

            m_ptr = a.m_ptr;
            m_count = a.m_count;

            ++(*m_count);
        }

        return *this;
        
    }

    int use_count(){
        return *m_count;
    }

    int * use_countPtr(){
        return m_count;
    }

    T* get(){
        return m_ptr;
    }
    


private:
    T *m_ptr;
    int *m_count;

};

int main() {

    struct Node{
        Node():pre(nullptr), next(nullptr) { }
        int data;
        sharedPtr<Node> pre;
        sharedPtr<Node> next;
    };


    int *c1 = nullptr;
    int *c2 = nullptr;

    { // 这里一个花括号,限制了作用域
        sharedPtr<Node> a(new Node);
        sharedPtr<Node> b(new Node);

        a->next = b;
        b->pre = a;

        // 把各自的m_count取出来
        c1 = a.use_countPtr();
        c2 = b.use_countPtr();

        cout << *c1 <<endl;
        cout << *c2 <<endl;

    }

    // 因为上边a 和 b在析构时,没有释放m_ptr和m_count,所以,下边两行可以访问,并且为1
    cout << *c1 <<endl;
    cout << *c2 <<endl;

    return 0;
}

对象a和b本身为sharedPtr类型,内部各包含一个Node*并且开辟了内存。为了方便后续描述,a中申请的这块内存称之为A_Node*,b中的这块内存称之为B_Node*

a和b初始化时,内部的引用计数均为1,因为这个时候只有自己使用自己内部的这块内存空间。
当执行了

a->next = b;
b->pre = a;

这两行代码以后,a和b内部的引用技术均为2。此时对于a来说,不仅仅自己使用A_Node*,同时b中的一个sharedPtr<Node>类型(与a同类型)的pre,也使用A_Node*,所以a中的引用计数为2,b中pre内部的引用计数也为2。

对于b来说,同理,不仅仅自己使用B_Node*,同时a中的一个sharedPtr<Node>类型(与b同类型)的next,也使用B_Node*,所以b中的引用计数为2,a中next内部的引用计数也为2。

重点来了,当程序执行到花括号外边的时候,a和b两个对象的生命周期到了,要执行各自的析构函数。但是!执行a的析构函数时,由于引用计数为2,减去1之后不为0,所以,不会释放a内部的m_ptr和m_count,只不过把m_count变为了1,由于引用计数机制的存在,此时b中的pre,这个智能指针内部的引用计数也会变为1。总结一下,就是a这个对象被析构了,但是 A_Node* 没释放。

同理,执行b的析构函数时,由于引用计数为2,减去1之后不为0,所以,不会释放b内部的m_ptr和m_count,只不过把m_count变为了1,由于引用计数机制的存在,此时a中的next,这个智能指针内部的引用计数也会变为1。总结一下,就是b这个对象被析构了,但是 B_Node* 没释放。

经过了a和b两个对象的析构函数,现在的情况是怎么样的?

A_Node*中的next指向B_Node*
B_Node*中的pre指向A_Node*

此时呢,形成了环。

在没有人为干预的情况下,想释放A_Node*,就必须先释放 B_Node*

因为一旦B_Node*释放,会触发B_Node*内部pre指针的析构函数,因此这时pre内部的引用计数为1,减1之后变为0,所以,可以释放掉pre所指向的内存空间,也就是会释放掉A_Node*,然后接着会触发A_Node*内部next指针的析构函数,同理,next指针会把自己指向的B_Node*给释放掉,这样,A_Node*和B_Node*这两块被a和b遗留下来的内存空间就可以正常被释放。

但是!!!问题来了,想释放B_Node*,就必须先释放 A_Node*

二者互相等待,都等待对方先释放,这个就是循环引用

解决办法
把Node中的两个shared_ptr换成weak_ptr即可,这样,a和b的引用计数均为1,可以正常是释放。

5. weak_ptr

是对shared_ptr的修改,实现逻辑如下代码。
实现的原则是:允许一块内存由多个指针拥有,与shared_ptr不同的是,不使用引用计数。
存在的问题:适用性不如shared_ptr广,因为一个weak_ptr释放的时候,就会把自己指向的区域释放掉,可能会重复释放。

// weak ptr ,与sharedPtr的不同在于,没有引用计数,总是,weakPtr,共享,但不计数,直接释放
template<class T>
class weakPtr{

public:
    weakPtr(T *a):m_ptr(a){ }

    ~weakPtr(){ 
        if(m_ptr) {
            delete m_ptr; 
            m_ptr = nullptr;
        }
        
    }

    // 赋值
    weakPtr<T> & operator= (const weakPtr<T> &a){
        if(m_ptr) delete m_ptr;
        m_ptr = a.m_ptr;
        return *this;
    }

    weakPtr<T> & operator= (T *a){
        m_ptr = a;
        return *this;
    }


    T& operator *(){
        return *m_ptr;
    }

    T* operator->(){
        return m_ptr;
    }


private:
    T *m_ptr;
};
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-11 16:27:42  更:2021-07-11 16:27:57 
 
开发: 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年5日历 -2024/5/4 21:50:44-

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