0%

C++智能指针

简要介绍一下C++11以后的智能指针,有std::unique_ptr<T>std::shared_ptr<T>std::weak_ptr<T>,并且废弃了std::auto_ptr<T>

首先智能指针是一个RAII类模型,用于动态分配内存,其设计思想是将基本类型指针封装为(模板)类对象指针,并在离开作用域时调用析构函数,使用delete删除指针所指向的内存空间。

智能指针的作用是,能够处理内存泄漏问题和空悬指针问题。

  • 对于unique_ptr,实现独占式拥有的概念,同一时间只能有一个智能指针可以指向该对象,因为无法进行拷贝构造和拷贝赋值,但是可以进行移动构造和移动赋值;

  • 对于shared_ptr,实现共享式拥有的概念,即多个智能指针可以指向相同的对象,该对象及相关资源会在其所指对象不再使用之后,自动释放与对象相关的资源;

  • 对于weak_ptr,解决shared_ptr相互引用时,两个指针的引用计数永远不会下降为0,从而导致死锁问题。而weak_ptr是对对象的一种弱引用,可以绑定到shared_ptr,但不会增加对象的引用计数。

1 unique_ptr

每个 unique_ptr 指针指向的堆内存空间的引用计数,都只能为 1,一旦该 unique_ptr 指针放弃对所指堆内存空间的所有权,则该空间会被立即释放回收。

  • 创建unique_ptr

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 创建空的unique_ptr
    std::unique_ptr<int> p1();
    std::unique_ptr<int> p2(nullptr);
    // 有指向的
    std::unique_ptr<int> p3(new int);
    // 创建空的unique_ptr
    std::unique_ptr<int> p4(new int);
    std::unique_ptr<int> p5(p4);//错误的,没有拷贝构造
    std::unique_ptr<int> p5(std::move(p4));//正确的,有移动构造

    值得一提的是,对于调用移动构造函数的 p4 和 p5 来说,p5 将获取 p4 所指堆空间的所有权,而 p4 将变成空指针(nullptr)。

  • unique_ptr 可以指向一个数组

    1
    2
    3
    4
    5
    6
    7
    std::unique_ptr<int[]> uptr = std::make_unique<int[]>(10);
    for (int i = 0; i < 10; i++) {
    uptr[i] = i * i;
    }
    for (int i = 0; i < 10; i++) {
    std::cout << uptr[i] << std::endl;
    }
  • C++14新增make_unique

1
2
unique_ptr<int> p1 = make_unique<int>(100);
auto p2 = make_unique<int>(200);
  • 将unique_ptr转换成shared_ptr类型
    转换条件:如果unique_ptr为右值时,他就可以赋值给shared_ptr。
    原因:因为shared_ptr包含一个显示构造函数,可用于将右值unique_ptr转换为shared_ptr,shared_ptr将接管原来归unqiue_ptr所拥有的对象。

  • unique_ptr的简单实现

    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
    template<typename T>
    class UniquePtr
    {
    public:
    UniquePtr(T *pResource = NULL)
    : m_pResource(pResource) {}
    ~UniquePtr(){ del();}
    public:
    void reset(T* pResource){// 先释放资源(如果持有), 再持有资源
    del();
    m_pResource = pResource;
    }
    T* release(){// 返回资源,资源的释放由调用方处理
    T* pTemp = m_pResource;
    m_pResource = nullptr;
    return pTemp;
    }
    T* get(){// 获取资源,调用方应该只使用不释放,否则会两次delete资源

    return m_pResource;
    }
    public:
    operator bool() const{ // 是否持有资源
    return m_pResource != nullptr;
    }
    T& operator * (){
    return *m_pResource;
    }
    T* operator -> (){
    return m_pResource;
    }
    private:
    void del(){
    if (nullptr == m_pResource) return;
    delete m_pResource;
    m_pResource = nullptr;
    }
    private:
    UniquePtr(const UniquePtr &) = delete; // 禁用拷贝构造
    UniquePtr& operator = (const UniquePtr &) = delete; // 禁用拷贝赋值
    private:
    T *m_pResource;
    };

2 shared_ptr

参考C++ 智能指针详解(二)——shared_ptr与weak_ptr

  • 特点总结如下

    • 共享所有权
    • 存储成本较裸指针多了个引用计数指针(相关控制块-共享)
    • 接口慎用(会导致蔓延问题)
    • 线程安全,引用计数增减会减慢多核性能
    • 支持拷贝构造,支持移动
  • shared_ptr使用注意事项

    • 避免循环引用,解决办法使用弱引用指针
    • 避免对原始指针多于两组的所有权(使用enable_shared_from_this<>避免this被多组所有权)
    • 避免在shared引用对象成员中再次使用shared_ptr,这样会导致资源重复释放
  • shared_ptr内存模型

  • API
函数API 含义
p.get() 返回p中保存的指针。小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了
p.use_count() 返回与p共享对象的智能指针数量;可能很慢,注意用于调试
p.unique() 若p.use_count()为1,返回true;否则返回false
p = q p和q都是shared_ptr,所保存的指针能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放
make_shared(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象
shared_ptr p(q) p是share_ptr q的拷贝;此操作会递增q中的计数器。q中的指针类型必须能转换为T*
shared_ptr p(u) p从unique_ptr u那里接管了对象的所有权;将u置为空
shared_ptr p(q,d) p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用的对象d来代替delete
shared_ptr p(p2,d) p是share_ptr p2的拷贝;此操作会递增p2中的计数器。p2中的指针类型必须能转换为T*,p将使用可调用的对象d来代替delete
p.reset() p置为空。若p是唯一指向其对象的shared_ptr,reset会释放此对象
p.reset(q) 传递了参数内置指针q,会令p指向q
p.reset(q,d) 传递了参数d,将会调用d而不是delete来释放q
p.swap(q)或swap(p,q) 交换两个智能指针

3 weak_ptr

  • API
函数API 含义
weak_ptr w 空weak_ptr可以指向类型为T的对象
weak_ptr w(sp) 与shared_ptr sp指向相同对象的weak_ptr。T必须能转换为sp指向的类型
w = p p可以是一个shared_ptr或一个weak_ptr。赋值后w与p共享对象
w.reset() 将w置为空
w.use_count() 与w共享对象的shared_ptr的数量
w.expired() 若w.use_count()为0,返回true,否则返回false
w.lock() 如果expired为true,返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr

感谢看到这里,在记录中收获成长,道阻且长