• C++ 11智能指针详解和使用



    0 引入

    • C++ 程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。但使用普通指针,容易造成内存泄露(忘记释放)、二次释放、程序发生异常时内存泄露等问题等。所以 C++11 就引入了智能指针

    • C 语言中最常使用的是malloc()函数分配内存,free()函数释放内存,而 C++ 中对应的是new、delete关键字。malloc()只是分配了内存,而new则更进一步,不仅分配了内存,还调用了构造函数进行初始化。


    1、定义

    C++11 中引入了智能指针(Smart Pointer):它利用了一种叫做 RAII(资源获取即初始化)的技术将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。这使得智能指针实质是一个对象,行为表现的却像一个指针。

    智能指针主要分为shared_ptr、unique_ptr和weak_ptr三种,使用时需要引用头文件。C++98 中还有auto_ptr,基本被淘汰了,不推荐使用。而 C++11 中shared_ptr和weak_ptr都是参考boost库实现的


    2、shared_ptr

    shared_ptr是使用最多的一种。多个智能指针可以指向同一个对象,即实现了资源的多指针共享。shared_ptr引入了计数机制,可以通过use_count成员函数查看资源所有者的个数。当一个shared_ptr指向该对象,则计数加一,当一个shared_ptr释放,计数减一。当最后一个指针释放,就释放内存。

    1.智能指针实现

    代码如下(示例):

    include<mutex>
    
    template<typename T>
    class Shared_ptr {
    private:
        // 成员变量
        T *ptr_{nullptr};
        int *ref_count_{nullptr};
        std::mutex *mutex_{nullptr};
    public:
        /******************** 构造函数和析构函数 ********************/
        // 默认构造函数
        constexpr Shared_ptr() noexcept = default;
    
        // 普通构造函数
        explicit Shared_ptr(T *ptr) : ptr_{ptr} {
            if (this->ptr_ != nullptr) {
                this->ref_count_ = new int{1};
                this->mutex_ = new std::mutex{};
            }
        }
        // 拷贝构造函数
        Shared_ptr(const Shared_ptr &rhs) noexcept: ptr_{rhs.ptr_}, ref_count_{rhs.ref_count_}, mutex_{rhs.mutex_} {
            if (this->ptr_ != nullptr) {
                this->addRefCount();
            }
        }
    
        // 移动构造函数
        Shared_ptr(Shared_ptr &&rhs) noexcept: ptr_{rhs.ptr_}, ref_count_{rhs.ref_count_}, mutex_{rhs.mutex_} {
            // right hand side is nullptr
            rhs.ptr_ = nullptr;
            rhs.ref_count_ = nullptr;
            rhs.mutex_ = nullptr;
        }
    
        // 析构函数
        ~Shared_ptr() noexcept {
            this->decrRefCount();
        }
    
        /******************** 运算符重载 ********************/
        // 拷贝赋值运算符
        Shared_ptr &operator=(const Shared_ptr &rhs) {
            Shared_ptr{rhs}.swap(*this);
            return *this;
        }
    
        // - move 本意为 "移动",但该函数并不能移动任何数据,它的功能很简单,就是将某个左值强制转化为右值。
        // - forward 完美转发,根据右值判断的推倒,调用forward 传出的值,若原来是一个右值,那么他转出来就是一个右值,否则为一个左值。这样的处理就完美的转发了原有参数的左右值属性,不会造成一些不必要的拷贝
        
        // 移动赋值运算符
        Shared_ptr &operator=(Shared_ptr &&rhs) noexcept {
            Shared_ptr{std::move(rhs)}.swap(*this);
            return *this;
        }
    
        // 取值
        T &operator*() const noexcept {
            return *this->ptr_;
        }
    
        // 取指针
        T &operator->() const noexcept {
            return this->ptr_;
        }
    
        // 到 bool 的隐式转换
        explicit operator bool() const noexcept {
            return static_cast<bool>(ptr_);
        }
    
        /******************** 成员函数 ********************/
        // 获取指针
        T *get() const noexcept {
            return ptr_;
        }
    
        // 获取引用计数
        int use_count() const noexcept {
            return this->ref_count_ == nullptr ? 0 : *this->ref_count_;
        }
    
        // 引用计数是否唯一
        bool unique() const noexcept {
            return *this->ref_count_ == 1;
        }
    
        // 重置指针
        void reset() noexcept {
            Shared_ptr{}.swap(*this);
        }
    
        // 重置指针
        void reset(T *ptr) {
            Shared_ptr{ptr}.swap(*this);
        }
    
        // swap函数
        void swap(Shared_ptr &rhs) noexcept {
            std::swap(this->ptr_, rhs.ptr_);
            std::swap(this->ref_count_, rhs.ref_count_);
            std::swap(this->mutex_, rhs.mutex_);
        }
    
    private:
        // 增加引用计数
        void addRefCount() {
            mutex_->lock();
            ++(*ref_count_);
            mutex_->unlock();
        }
    
        // 减少引用计数,如果为零,则释放指针
        void decrRefCount() {
            bool deleteflag = false;
            mutex_->lock();
            if (--(*ref_count_) == 0) {
                delete ptr_;
                delete ref_count_;
                deleteflag = true;
            }
            mutex_->unlock();
            if (deleteflag) {
                delete mutex_;
            }
        }
    };
    
    // 函数模板
    template<typename T>
    auto make_Shared(T v) {
        return Shared_ptr<T>{new T(v)};
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135

    在这里插入图片描述

    2.常见使用场景

    2.1基本使用方法

    代码如下(示例):

    	    // 初始化
            shared_ptr<int> p1 = make_shared<int>(42);
            shared_ptr<int> p2(new int(1024));
            //上面用构造函数创建,等价于
            //int* ptr = new int(1024);
            // std::shared_ptr p1(ptr);
            // std::shared_ptr p2(ptr);//错误 不能同时赋值给多个智能指针
            shared_ptr<int> p3 = p2;
            // 获取值
            cout << p1.get() << endl;
            cout << *p1.get() << endl;
            cout << *p1 << endl;
            // 引用计数
            cout << p1.use_count() << endl;
            cout << p2.use_count() << endl;
            cout << p3.use_count() << endl;
            // 引用计数是否唯一
            cout << p1.unique() << endl;
            // 重置
            p1.reset(new int(322));
            // swap
            p1.swap(p2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 同一普通指针不能同时为多个 shared_ptr 对象赋值,否则会导致程序发生异常。

    2.2大量复杂数据结构在多处引用

    在大量数据转移时候,有时候可能转几手,自己都忘记如何释放,或者在不同线程中,之前的做法为了小心翼翼的避免内存泄露,都要一一对应,现在只需要用智能指针去处理,不用担心释放问题,(实际过程中需要避免循环引用)
    代码如下(示例):

     //暂无,没有想好怎么表达
    
    • 1

    3、weak_ptr

    弱指针主要是为了避免shared_ptr循环引用问题:当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

    当出现循环引用时,把其中一个shared_ptr转化成weak_ptr即可解决问题。

    		shared_ptr<int> p = make_shared<int>(100);
            weak_ptr<int> w(p);    // 与p指向相同对象的weak_ptr,
            w = p;                 // p可以是shared_ptr或者weak_ptr,赋值后w和p共享对象
            w.reset();             // weak_ptr置为空
            w.use_count();         // 与w共享对象的shared_ptr的计数
            w.expired();           // w.use_count()为0则返回true,否则返回false
            w.lock();              // w.expired()为true,返回空的shared_ptr;否则返回指向w的shared_ptr
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    4、unique_ptr

    unique_ptr相对于其他两个智能指针更加简单,它和shared_ptr使用差不多,但是功能更为单一,它是一个独占型的智能指针,不允许其他的智能指针共享其内部的指针,更像原生的指针(但更为安全,能够自己释放内存)。不允许赋值和拷贝操作,只能够移动


    5、总结和引用

    5.1 缺点

    使用智能指针虽然能够解决内存泄漏问题,但是也付出了一定的代价。以shared_ptr举例:

    • shared_ptr的大小是原始指针的两倍,因为它的内部有一个原始指针指向资源,同时有个指针指向引用计数。
    • 引用计数的内存必须动态分配。虽然一点可以使用make_shared()来避免,但也存在一些情况下不能够使用make_shared()。
    • 增加和减小引用计数必须是原子操作,因为可能会有读写操作在不同的线程中同时发生。

    5.2 引用

    1、c++11智能指针解析——揭开底层面纱,完整理解智能指针
    2、C++11 智能指针【详解+实现】【面试常考】


  • 相关阅读:
    文举论金:非农到来!黄金原油全面走势分析策略独家指导
    汽车电子技术栈
    上来就对标 20k Star 的开源项目,是自不量力还是后起之秀?
    docker容器保持运行不退出
    编程语言“鄙视链” +1?亚马逊力捧 Rust,Go 技术负责人连发 14 条推特抵制“拉踩”
    DevOps, HybridOps and AIOps浅谈
    C++ 编译运行opencv4代码踩坑及解决
    安卓判断是否是模拟器,适配主流雷电,MUMU,夜神,逍遥
    vue截取URL中的参数
    【建造者设计模式详解】Java/JS/Go/Python/TS不同语言实现
  • 原文地址:https://blog.csdn.net/ljsant/article/details/126957266