智能指针的相关使用 C++11

66 阅读4分钟

为什么要有智能指针?

常规的裸指针 type *ptr 在使用的时候需要手动为其分配和释放空间

{
    int *A = NULL;
    A = (int *)malloc(sizeof(int) * 10);
    ...
    free(A);   // 如果没有手动释放,离开作用域后,该处分配的内存会造成内存泄露
    A = NULL;
}

智能指针的出现就是为了避免由于忘记手动释放导致的内存泄露出现,更好的管理堆内存

常见的智能指针

C++里面的四个智能指针: auto_ptr, unique_ptr, shared_ptr, weak_ptr 其中后三个是C++11支持, 并且auto_ptr已经被C++11弃用。

shared_ptr共享智能指针

  • shared_ptr采用引用计数的方式实现了内存的自动管理,每一次shared_ptr的拷贝都指向相同的内存位置,每拷贝一次,引用计数就会加1,每一个拷贝得到的shared_ptr执行析构或者reset()时,引用计数就会减1,当最后一个share_ptr析构时,内存就会被释放。
  • 创建共享指针
// 优先使用make_shared()创建指针
auto sp = std::make_shared<int>(100);

// 相当于
shared_ptr<int> sp(new int(100));

//或者通过reset对未初始化的shared_ptr进行重置
shared_ptr <int>sp;
sp.reset(new int(100));

//不能采用常规指针的分配方式
shared_ptr<int> p = new int(100);   // 错误
  • 释放共享指针
auto sp = std::make_shared<int>(100);
sp.reset(); // 在sp存在引用计数的情况下,调用reset()引用计数将减1

sp.use_count(); // 查看当前引用计数

//shared_ptr不支持对数组的自动管理,需要对数组类型进行删除时,需要指定删除器,即自行实现删除过程,可以通过函数或者lambda表达式进行
void DeleteSpace(int *p){
    delete []p;
    
    // 非数组指针也可以通过删除器手动释放
    delete p;
}
shared_ptr<int> sp(new int[10], DeleteSpace);

//lambda表达式实现删除器
shared_ptr<int> sp1(new int(1), [](int *p){delete [p];})
  • 共享指针使用需要注意的几个问题
    • 不要通过裸指针初始化多个shared_ptr
int *p1 = new int(1);
shared_ptr<int> sp1 = p1;  // 可以
shared_ptr<int> sp2 = p1;  // 错误
  • 不要通过构造this指针的共享指针的方式来获取this指针
    {
        // 此时在这里就创建了一个新的shared_ptr, 离开作用域后,引用计数减1变为0, this对象被释放,而实际对象的创建是在外部的,会造成二次析构的问题
        return shared_ptr<A>(this);
        
        // 正确的做法应该是通过A类继承std::enable_shared_from_this,enable_shared_from_this类中会创建一个weak_ptr来监视管理this的shared_ptr,weak_ptr的创建销魂不会影响shared_ptr的引用计数,所以不会出现上面的问题。关于weak_ptr的相关内容,后面会讲述。
    }
  • 避免循环引用,会导致内存泄露
class A
{
public:
    std::shared_ptr<B> bptr;
}

class B
{
public:
    std::shared_ptr<A> aptr;
}

int main()
{
    {
    std::shared_ptr<A> ap(new A);  // ap引用计数为1
    std::shared_ptr<B> bp(new B);  // bp引用计数为1
    ap->bptr = bp; // bp 引用计数 +1,变为2
    bp->aptr = ap; // ap 引用计数 +1,变为2
    }
    // 离开作用域之后
    // ap bp 被释放,引用计数各 -1,没有变为0,此时AB不会被析构释放
    // 解决方式是将aptr 或者 bptr任意一个改成weak_ptr,具体原因看weak_ptr系相关内容
}

unique_ptr独占智能指针

  • 独占即意味着不能像shared_ptr一样通过复制来创建新的指针指向同一个内存地址
unique_ptr<int> up(new int(1));
unique_ptr<int> up2 = up;  // 错误

// C++11中只提供了std::make_shared的创建方式,std::make_unique的初始化方法在C++14中被加入
  • 虽然不能通过赋值的方式将指针转移,但是C++11提供了std::move()可以实现指针的转移
unique_ptr<int> up(new int(1));
unique_ptr<int> other_up = std::move(ip);
  • 删除器的指定与shared_ptr有所不同
shared_ptr<int> sp(new int(10), [](int *p){ delete p;}); //正确
unique_ptr<int> up(new int(10), [](int *p){ delete p;}); //错误
// 正确的写法需要指定其删除器的类型,包括返回值和参数类型,即对应的函数指针
unique_ptr<int, void (*)(int*)> up2(new int (10), [](int *p){delete p;}); 

weak_ptr 弱引用智能指针

前面提到过这个指针解决了shared_ptr可能存在的内存泄露问题,其原理就是weak_ptr的创建销毁不会影响引用计数,内部则是通过weak_ptr访问shared_ptr,来实现对资源的操作。weak_ptr就是对shared_ptr的监视作用

  • 可以观察当前资源的引用计数
shared_ptr <int> sp(new int(10));
weak_ptr<int> wp(sp);
wp->use_count();    // 结果为1
  • 可以判断当前资源是否已经被释放
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
if(wp->expired())
{
    cout << "当前资源已释放"
}
  • 可以返回当前资源的shared_ptr
shared_ptr<int> sp(new int(10));
weak_ptr<int> wp(sp);
auto sp1 = wp.lock();
  • 之前提到的使用weak_ptr解决返回this指针时造成二次析构的问题,这里的lock就是实现的关键。
  • 在enable_shared_from_this类中返回this指针时,调用的就是lock()方法,返回其监视的shared_ptr,引用计数加1;而不会是由于根据this构造新的共享指针而导致内部析构
  • 同样的原理,weak_ptr的创建不会影响引用计数,那么shared_ptr的循环引用导致的内存泄露问题也可以被解决
  • 这里得到一个关键点,weak_ptr必须在shared_prt被成功创建且存在引用计数的情况下才能有效果。因为weak_ptr所有的功能都是基于shared_ptr实现的。所以在使用weak_ptr之前需要调用wp.expire()先判断当前资源可不可以访问到