【学习总结】智能指针shared_ptr和unique_ptr使用汇总

发布时间 2023-03-26 16:13:03作者: 藤原豆腐店の張さん

1.shared_ptr

1.1 shared_ptr介绍

shared_ptr主要用于托管动态分配的内存。
在程序中动态分配了一块内存,这块内存可以是变量可以是对象,为了避免内存泄露,我们必须在整个程序的所有可能跑到的分支,保证这块内存不用了之后可以得到正确的释放。
普通指针使用起来麻烦,而且使用不当还很有可能出现程序崩溃,比如指针未释放导致内存泄漏、踩空指针或者指针重复释放。
智能指针可以有效地避免这个问题。

1.2 shared_ptr底层实现

shared_ptr指向同一个对象的时候,会共享一个共享计数,如上面的示例程序,计数一开始到了3,当sp1调用reset()后,计数-1,直到三个shared_ptr都被reset了,计数归零,对象销毁。

1.3 shared_ptr使用方法

  1. 初始化
std::shared_ptr<int> p1;             //不传入任何实参
std::shared_ptr<int> p2(nullptr);    //传入空指针 nullptr

std::shared_ptr sp1(new Z(1));
简单解释一下:这里new了一个Z类型地对象(我们先称之为Z(1)),并且用智能指针sp1来托管这个对象。
我们要调用Z(1)这个对象,只需要和普通指针一样用sp1->即可,使用完也不用释放,程序销毁地时候会自动调用Z的析构函数。
可以使用如下的方法将对象Z(1)托管给多个智能指针:

// 拷贝构造函数,sp1 sp2 sp3共享同一个指针和指针计数
std::shared_ptr<Z> sp2 = sp1;
std::shared_ptr<Z> sp3(sp1);
// 移动构造函数,sp1指向的对象被移动给了sp4,sp1变为空智能指针,计数清零
std::shared_ptr<Z> sp4(std::move(sp1));
std::shared_ptr<Z> sp5 = std::make_shared<Z>();
  1. get()
    可以用get()函数获取Z(1)这个对象的地址(普通指针):
    Z* p = sp1.get();
  2. reset()
    可以用reset()重置sp1托管的对象,如果此前sp1指向的对象没有其他智能指针托管,那么该对象会直接被释放:
    sp1.reset(new Z(2));
  3. use_count()
    用于获取当前智能指针指向的对象的所有智能指针个数。
  4. unique()
    判断当前是否还有其他的智能指针指向该智能指针指向的对象。

1.4 使用案例

  1. 示例程序
#include <iostream>
#include <memory>

class Z
{
public:
    int i;
    Z(int z) :i(z) {}
    ~Z() { std::cout << i << " destructed\n"; }
};
int main()
{
    std::shared_ptr<Z> sp1(new Z(1));
    std::shared_ptr<Z> sp2(sp1);
    std::shared_ptr<Z> sp3 = sp1;
    std::cout << "sp1->i=" << sp1->i << ",sp2->i=" << sp2->i << ",sp3->i=" << sp3->i << "\n";
    Z* p = sp1.get();
    std::cout << "p->i=" << p->i << "\n";
    sp1.reset(new Z(2));
    std::cout << "sp1->i=" << sp1->i << "\n";
    sp2.reset(new Z(3));
    std::cout << "sp2->i=" << sp2->i << "\n";
    sp3.reset(new Z(4));
    std::cout << "sp3->i=" << sp3->i << "\n";

    return 0;
}
  1. 输出结果
sp1->i=1,sp2->i=1,sp3->i=1
p->i=1
sp1->i=2
sp2->i=3
1 destructed
sp3->i=4
4 destructed
3 destructed
2 destructed
  1. 说明
    在sp1、sp2和sp3都被reset给了其他的对象之后,原来的Z(1)就被释放掉了。
    剩余的对象则是在退出程序的时候释放。

1.5 注意事项

  1. 不能使用如下的方式初始化,sp1和sp2初始化的时候共享计数并不会变成2,而是都记成1,这样sp1销毁之后指针p已经销毁了,sp2销毁的时候会导致指针重复释放,导致程序崩溃。
Z* p = new Z(1);
std::shared_ptr<Z> sp1(p), sp2(p);
  1. 可以自己定义堆内存的释放函数,这样对象在析构的时候就会优先调用我们定义的析构函数。
std::shared_ptr<Z> sp1(new Z(1), std::default_delete<Z>()); // 使用默认的delete函数

void deleteZ(Z* p) {
  delete p;
}
std::shared_ptr<Z> sp2(new Z(2), deleteZ);

2. unique_ptr

2.1 unique_ptr介绍

unique_ptr的功能基本和shared_ptr相同,也是用于管理堆内存的智能指针,但是不允许多个unique_ptr指向同一个对象。

2.2 unique_ptr使用方法

  1. 初始化
std::unique_ptr<Z> up1;
std::unique_ptr<Z> up2(nullptr);
std::unique_ptr<Z> up1(new Z(1));
std::unique_ptr<Z> up2(up1); // 错误,不能使用两个unique_ptr指向同一个对象
std::unique_ptr<Z> up3(std::move(up1)); // 正确,可以是用移动构造函数,up1被释放为空指针
  1. get()
    可以用get()函数获取Z(1)这个对象的地址(普通指针):
    Z* p = up1.get();
  2. reset()
    可以用reset()重置sp1托管的对象,如果此前sp1指向的对象没有其他智能指针托管,那么该对象会直接被释放:
    up1.reset(new Z(2));
  3. release()
    释放当前的智能指针,但是不释放该智能指针指向的堆内存。
  4. get_deleter()
    获取当前智能指针的销毁函数。

2.3 其他不作赘述,和shared_ptr类似。