智能指针及其作用

一、引入背景

使用 C++ 的指针可以动态开辟存储空间,但若在使用完毕后忘记释放(或在释放之前,程序 throw 出错误,导致没有释放),导致该内存单元一直被占据直到程序结束,即发生了所谓的内存泄漏
【注】内存泄漏是指堆内存的泄漏。堆,就是那些由 new 分配的内存块。
  因此智能指针的作用就是为了保证使用堆上对象的时候,对象一定会被释放,但只能释放一次,并且释放后指向该对象的指针应该马上归 0

二、C++11 以前的智能指针

auto_ptr,指向一个动态分配的对象指针,它的析构函数用于删除所指对象的空间,以此达到对对象生存期的控制。
  已被弃用,原因是:

  • 避免潜在的内存崩溃:auto_ptr 进行赋值操作时候,被赋值的取得其所有权,去赋值的丢失其所有权(变成空指针,无法再使用);
  • 不够方便:没有移动语义(后面讲)。

三、C++11 标准的智能指针

加入了 unique_ptr、shared_ptr 和 weak_ptr。

  1. unique_ptr,等于 boost 库中的 scoped_ptr,正如其名字所述,scoped_ptr 所指向的对象在作用域之外会自动得到析构
    【注】boost 库是 Boost 社区在 C++11 之前嫌弃标准更新太慢,而自发组织开发、维护的一个扩展库。通过包含头文件 ”boost/scoped_ptr.hpp” 来引入。
    简单定义:
namespace boost
{
    // scoped_ptr 是 non-copyable 的
    // 也就是说你不能去尝试复制一个 scoped_ptr 的内容到另外一个 scoped_ptr 中
    // 这也是为了防止错误地多次析构同一个指针所指向的对象
    template<typename T> class scoped_ptr : noncopyable

    {
    private:

        T *px;
                                             
        // scoped_ptr 不能通过其他 scoped_ptr 共享控制权
        // 因此在 scoped_ptr 类的内部将拷贝构造函数和 = 运算符重载定义为 private
        scoped_ptr(scoped_ptr const &);
        scoped_ptr &operator=(scoped_ptr const &);

        typedef scoped_ptr<T> this_type;

        void operator==( scoped_ptr const & ) const;
        void operator!=( scoped_ptr const & ) const;

    public:

        // 构造函数显示定义,不能通过隐式转换(赋值操作符 =)构造
        explicit scoped_ptr(T *p = 0);
        ~scoped_ptr();

        explicit scoped_ptr( std::auto_ptr<T> p ): px( p.release() );
        // 在 scoped_ptr 离开作用域之前也可以显式销毁它们所管理的对象,调用它的 reset 方法即可
        void reset(T *p = 0);

        T &operator*() const;
        T *operator->() const;
        T *get() const;

        // 虽然 scoped_ptr 不能转移控制权,但是它们可以交换控制权
        void swap(scoped_ptr &b);
    };

    template<typename T>
    void swap(scoped_ptr<T> &a, scoped_ptr<T> &b);
}
  1. shared_ptr,可共享指针对象,可以赋值给 shared_ptr 或 weak_ptr。shared_ptr 中所实现的本质是引用计数,拷贝一个 shared_ptr 将对这个智能指针的引用次数加 1,而当这个智能指针的引用次数降低到 0 的时候,该对象自动被析构。
    代码部分与 scoped_ptr 类似,只是支持拷贝、赋值和==、!=。

  2. weak_ptr
    weak_ptr 和 shared_ptr 的最大区别在于 weak_ptr 在指向一个对象的时候不会增加其引用计数,因此你可以用 weak_ptr 去指向一个对象并且在 weak_ptr 仍然指向这个对象的时候析构它,此时你再访问 weak_ptr 的时候,weak_ptr 返回的会是一个空的 shared_ptr。
    常被用来解决环状引用问题,告诉 C++ 环上哪一个引用是最弱的,因此在一个环上把原来的某一个 shared_ptr 改成 weak_ptr 就可以了。

【注】以上 std 标准库的智能指针都可以通过包含头文件 <memory> 来引入。

四、智能指针 unique_ptr 和 shared_ptr 的使用选择

区别
我们都知道 unique_ptr 是独占所指向对象的,也就是说某个时刻只能有一个 unique_ptr 指向一个给定对象。如果这个对象需要在多个指针之间分享,可以把它转变成 shared_ptr。如果是 unique_ptr,当它被销毁时它所指向的对象也被销毁。但 shared_ptr 会维护一个引用计数(use_count),只有最后一个 shared_ptr 也被销毁时,这个对象才真的被销毁。

使用场景
尽管 shared_ptr 功能强大,但不是任何时候都有必要使用 shared_ptr。当你不确定使用的指针是不是被分享所有权的时候,默认选 unique_ptr 独占式所有权,因为 unique_ptr 效率比 shared_ptr 高,不需要维护引用计数和背后的控制块。当确定要被分享的时候可以转换成 shared_ptr。

另外其实不是说两个指针分享同一个变量就需要用 shared_ptr,尤其是不涉及到多线程的程序中,不使用智能指针也没关系。

有必要使用 shared_ptr 的场景举例:

----------------------------------------
|               Thread 1               |
| ------------------------------------ |
| void func(shared_ptr<int> p) { ... } |
----------------------------------------

-------------------------------------------
|              Main Thread                |
| --------------------------------------- |
| shared_ptr<int> p = make_shared<int>(); |
| func(p);                                |
-------------------------------------------

主线程开启子线程并在其中执行 func 函数,如果不使用智能指针 shared_ptr,当主线程执行完自行释放普通指针 p,此时子线程的函数还没执行完,p 就已经失效了,造成程序崩溃。而使用 shared_ptr 只会在主线程结束时自动将 use_count 减一,不影响子线程的使用。

参考文献:
[1] shared_ptr 和 unique_ptr 区别和联系
[2] 智能指针:shared_ptr 和 unique_ptr,优先使用 unique_ptr

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • C++ 智能指针详解 一、简介由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 de...
    yangqi916阅读 1,386评论 0 2
  • 1. 什么是智能指针? 智能指针是行为类似于指针的类对象,但这种对象还有其他功能。 2. 为什么设计智能指针? 引...
    MinoyJet阅读 646评论 0 1
  • 导读## 最近在补看《C++ Primer Plus》第六版,这的确是本好书,其中关于智能指针的章节解析的非常清晰...
    小敏纸阅读 2,020评论 1 12
  • 资源管理在软件开发中历来都是老大难问题,堆上内存的管理尤其麻烦。现代编程语言引入了垃圾回收机制,如 Java,Py...
    hanpfei阅读 1,740评论 0 0
  • 原作者:Babu_Abdulsalam 本文翻译自CodeProject,转载请注明出处。 引入### Ooops...
    卡巴拉的树阅读 30,171评论 13 74