前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >c++智能指针的理解与简易实现

c++智能指针的理解与简易实现

原创
作者头像
腹黑熊
修改2022-05-10 17:46:22
7330
修改2022-05-10 17:46:22
举报
文章被收录于专栏:大熊爱学习大熊爱学习

总结下个人对智能指针的理解,手写一个简易的c++实现,最后整理一下相关知识点,有不准确的部分欢迎大佬指正。

智能指针与动态资源管理

动态资源的管理一直是一个头疼的问题,因为动态内存的特殊性,其并不会在程序的运行过程中自动进行释放,那么在动态内存上构造的对象也就不会进行析构,所以早期的动态对象的管理需要开发人员自己去确定该动态对象最后的使用时间,并在确定没有变量对其操作后主动调用delete或delete[]进行释放。而这个确定释放时间的过程是非常容易出错的,可能是开发人员无意识的疏忽,也可能是异常导致的流程跳转,最后导致内存泄露或者重复释放。内存泄露会使该部分内存资源不可用,以及同样重要的,动态对象所持有的资源无法释放。而重复释放则可能会导致程序crash。

于是智能指针应运而生,承担了删除动态对象释放内存的责任。智能指针利用c++ RAII的特性和模板化编程,本质上是一个包装类,使用起来像普通指针一样方便。当最后一个引用动态对象的智能指针离开作用域或不在引用动态对象后对其进行清理


智能指针与动态对象所有权

就像上面提到的,无论是手动管理还是智能指针,都需要在确定最后一个引用该动态对象的指针释放后清理。于是顺势就引出了所有权问题,当一个动态对象只会被一个指针引用时为独占所有权,被多个指针引用时为共享所有权。独占所有权的指针在释放时直接删除对象,共享所有权的指针则在最后一个指针释放时删除对象。其实可以看出来,独占指针就是一种特殊的共享指针,之所以在使用时进行区分也是考虑到各自的代码复杂程度,独占指针的实现要更简单,资源占用更少。


智能指针简单实现

这里为了图省事只实现了构造函数、析构函数和基本的运算符,仅供参考。

unique_ptr

代码语言:c++
复制
template <typename T>
class unique_ptr {
public:
    unique_ptr() = default;
    unique_ptr(std::nullptr_t) {}

    explicit unique_ptr(T* t) { // 单参构造函数通过explicit禁止隐式转换
        _p = t;
    }

    unique_ptr(const unique_ptr<T>&) = delete;

    unique_ptr(unique_ptr<T>&& t) {
        this->_p = t._p;
        t._p = nullptr;
    }

    ~unique_ptr() {
        if(_p) {
            delete _p;
        }
    }

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

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

    unique_ptr<T>& operator=(unique_ptr<T>&& t) {
        if(_p) {
            delete _p;
        }

        _p = t._p;
        t._p = nullptr;
        return *this;
    }

    unique_ptr<T>& operator=(std::nullptr_t) {
        if(_p) {
            delete _p;
        }

        _p = nullptr;
        return *this;
    }

private:
    T* _p{nullptr};
};

template <typename T>
class unique_ptr<T[]> {
public:
    unique_ptr() = default;
    unique_ptr(std::nullptr_t) {}

    explicit unique_ptr(T* t) {
        _p = t;
    }

    unique_ptr(const unique_ptr<T[]>&) = delete;

    unique_ptr(unique_ptr<T[]>&& t) {
        this->_p = t._p;
        t._p = nullptr;
    }

    ~unique_ptr() {
        if(_p) {
            delete[] _p;
        }
    }

    T& operator[](std::size_t i) {
        return _p[i];
    }

    unique_ptr<T[]>& operator=(unique_ptr<T[]>&& t) {
        if(_p) {
            delete[] _p;
        }

        _p = t._p;
        t._p = nullptr;
        return *this;
    }

    unique_ptr<T[]>& operator=(std::nullptr_t) {
        if(_p) {
            delete[] _p;
        }

        _p = nullptr;
        return *this;
    }

private:
    T* _p{nullptr};
};

shared_ptr

代码语言:c++
复制
class shared_count{
public:
    shared_count() = default;
    std::mutex m;
    size_t ref_count{1};
};

template <typename T>
class shared_ptr{
public:
    shared_ptr() = default;

    shared_ptr(std::nullptr_t) {};

    explicit shared_ptr(T* t) {
        _shr_cnt = new shared_count();
        _p = t;
    };

    shared_ptr(const shared_ptr<T>& t) {
        if(t._shr_cnt) {
            std::unique_lock<std::mutex> lock(t._shr_cnt->m);
            t._shr_cnt->ref_count += 1;
            this->_shr_cnt = t._shr_cnt;
            this->_p = t._p;
        }
    }

    shared_ptr(shared_ptr<T>&& t) {
        if(t._shr_cnt) {
            this->_shr_cnt = t._shr_cnt;
            this->_p = t._p;
            t._shr_cnt = nullptr;
            t._p = nullptr;
        }
    }

    shared_ptr<T>& operator=(const shared_ptr<T>& t) {
        if(this == &t) {
            return *this;
        }

        _release();
        if(t._shr_cnt) {
            std::unique_lock<std::mutex> lock(t._shr_cnt->m);
            t._shr_cnt->ref_count += 1;
            this->_shr_cnt = t._shr_cnt;
            this->_p = t._p;
        }
        return *this;
    }

    shared_ptr<T>& operator=(shared_ptr<T>&& t) {
        if(this == &t) {
            return *this;
        }

        _release();
        if(t._shr_cnt) {
            this->_shr_cnt = t._shr_cnt;
            this->_p = t._p;
            t._shr_cnt = nullptr;
            t._p = nullptr;
        }
    }

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

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

    ~shared_ptr() {
        _release();
    }

private:
    T* _p{nullptr};
    shared_count* _shr_cnt{nullptr};

    void _release() {
        if(_shr_cnt) {
            std::unique_lock<std::mutex> lock(_shr_cnt->m);
            _shr_cnt->ref_count -= 1;
            if(_shr_cnt->ref_count == 0) {
                delete _shr_cnt;
                delete _p;
            }
            _shr_cnt = nullptr;
            _p = nullptr;
        }
    }
};

简易实现的小总结

  • 代码中可以看到,使用unique_ptr与使用裸指针尺寸相同,空间上没有变化。而shared_ptr的尺寸是裸指针的二倍,同时还有控制块的开销,相比较unique_ptr要更占空间。实际的c++标准库也是如此,共享指针更占用资源。
  • 虽然我这里专门根据数组类型进行unique_ptr特化,但是只是写出来方便理解,并不建议使用,实际使用时管理独占动态数组还是推荐容器std::vector。shared_ptr因为偷懒没有对数组类型做特化,但是依然不推荐使用shared_ptr管理共享动态数组,推荐shared_ptr<vector<T>>的方式使用。智能指针管理c风格动态数组一般只考虑在一些调用c接口返回c风格动态数组的时候可以使用。
  • 智能指针构造函数利用explicit声明来禁止隐式转换,主要考虑到一些无法确定转换类型的场景。考虑一种情况:如果一个函数同时出现了unique_ptr<T>和unique_ptr<T[]>的重载,并且尝试通过隐式转换调用,那么编译器无法推测应该转换为哪种类型。
  • 最后这里的实现仅供学习参考,距离真正的可应用还是有差距的。像不支持自定义分配器,不支持自定义析构器,不支持weak_ptr,不支持shared_ptr别名,不支持工厂函数构建等等,可优化空间很多。

浅总结一下智能指针知识点

  • shared_ptr一般比unique_ptr更占用内存,但是如果在unique_ptr使用自定义析构器的话,那么情况可能不同。
  • 使用const声明unique_ptr的话,可以限定资源只作用于当前作用域,无法被移动。
  • weak_ptr只是引用shared_ptr的控制块,有一个单独的count统计当前控制块引用次数。shared_ptr管理的资源会在所有shared_ptr销毁后释放,但是控制块需要等所有weak_ptr销毁后再释放(前提:不是由make函数创建的对象)。
  • shared_ptr引用计数是线程安全的,但是不保证引用对象的多线程安全,需要参数类型自行处理。
  • shared_ptr可以由unique_ptr右值引用构造,所以对象的工厂函数如果不确定所有权的话可以一律使用unique_ptr返回。
  • 避免使用裸指针构造智能指针,尽量使用工厂函数:
    • 避免重复的类型声明。
    • 可能会出现多个智能指针引用同一个动态对象的误操作的情况,最后导致重复释放。
    • 针对shared_ptr,工厂函数只进行一次动态内存分配,分配效率高,同时也避免了裸指针方式两次分配中间因为异常导致的内存泄露。
  • 如下场景不适合或谨慎使用make工厂函数:
    • 自定义析构器。工厂函数无法自定义析构器,所以这种场景就无法使用。
    • 针对shared_ptr,如果使用weak_ptr进行监控的话,要确保weak_ptr在shared_ptr都销毁后尽快销毁,否则不建议使用工厂函数。
  • 如上一节所说,动态数组尽量使用vector,只有在一些调用c接口返回c风格动态数组的时候才使用智能指针管理。

参考:libstdc++cppreference

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 智能指针与动态资源管理
  • 智能指针与动态对象所有权
  • 智能指针简单实现
    • unique_ptr
      • shared_ptr
        • 简易实现的小总结
        • 浅总结一下智能指针知识点
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
        http://www.vxiaotou.com