C++智能指针的使用:shared_ptr、weak_ptr、unique_ptr的使用,使用案例说明。

系列文章目录

本章内容:
(1)shared_ptr、weak_ptr、unique_ptr的介绍
(2)单独使用share_ptr造成的内存泄漏
(3)shared_ptr和weak_ptr的配合使用



前言

C/C++ 为了实现对内存的细粒度的操作,没有设计垃圾收集器。因此,使用 C/C++ 编写项目时,开发人员需要格外注意内存的申请和释放。本文介绍了shared_ptrweak_ptr的使用,分析了它们对内存泄漏的检测方式,并指出它们所存在的不足。希望读者可以通过改进指针算法或 share_ptr 来规避内存泄漏,尽量不要写出连检测算法也无法处理的代码结构。


一、shared_ptr、weak_ptr、unique_ptr的使用

1.1 shared_ptr

share_ptr是C++11新添加的智能指针,它限定的资源可以被多个指针共享

只有指向动态分配的对象的指针才能交给 shared_ptr 对象托管。将指向普通局部变量、全局变量的指针交给 shared_ptr 托管,编译时不会有问题,但程序运行时会出错,因为不能析构一个并没有指向动态分配的内存空间的指针。

1.2 weak_ptr

C++11 weak_ptr智能指针和 shared_ptr、unique_ptr 类型指针一样,weak_ptr 智能指针也是以模板类的方式实现的。weak_ptr( T 为指针所指数据的类型)定义在头文件,并位于 std 命名空间中。因此,要想使用 weak_ptr 类型指针,程序中应首先包含如下 2 条语句:

#include <memory>
using namespace std;
// 第 2 句并不是必须的,可以不添加,则后续在使用 unique_ptr 指针时,必须标注std::。

需要注意的是,C++11标准虽然将 weak_ptr 定位为智能指针的一种,但该类型指针通常不单独使用(没有实际用处),只能和 shared_ptr 类型指针搭配使用。甚至于,我们可以将 weak_ptr 类型指针视为 shared_ptr 指针的一种辅助工具,借助 weak_ptr 类型指针, 我们可以获取 shared_ptr 指针的一些状态信息,比如有多少指向相同的 shared_ptr 指针、shared_ptr 指针指向的堆内存是否已经被释放等等。

需要注意的是,当 weak_ptr 类型指针的指向和某一 shared_ptr 指针相同时,weak_ptr 指针并不会使所指堆内存的引用计数加 1;同样,当 weak_ptr 指针被释放时,之前所指堆内存的引用计数也不会因此而减 1。也就是说,weak_ptr 类型指针并不会影响所指堆内存空间的引用计数。

除此之外,weak_ptr 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。

1、weak_ptr指针的创建

创建一个 weak_ptr 指针,有以下 3 种方式:

  1. 可以创建一个空 weak_ptr 指针,例如:
std::weak_ptr<int> wp1;
  1. 凭借已有的 weak_ptr 指针,可以创建一个新的 weak_ptr 指针,例如:
std::weak_ptr<int> wp2 (wp1);

若 wp1 为空指针,则 wp2 也为空指针;反之,如果 wp1 指向某一 shared_ptr 指针拥有的堆内存,则 wp2 也指向该块存储空间(可以访问,但无所有权)。

  1. weak_ptr 指针更常用于指向某一 shared_ptr 指针拥有的堆内存,因为在构建 weak_ptr 指针对象时,可以利用已有的 shared_ptr 指针为其初始化。例如:
std::shared_ptr<int> sp (new int);
std::weak_ptr<int> wp3 (sp);

由此,wp3 指针和 sp 指针有相同的指针。再次强调,weak_ptr 类型指针不会导致堆内存空间的引用计数增加或减少。

2、weak_ptr模板类提供的成员方法

和 shared_ptr、unique_ptr 相比,weak_ptr 模板类提供的成员方法不多

weak_ptr指针可调用的成员方法

operator=()重载 = 赋值运算符,是的 weak_ptr 指针可以直接被 weak_ptr 或者 shared_ptr 类型指针赋值。
swap(x)其中 x 表示一个同类型的 weak_ptr 类型指针,该函数可以互换 2 个同类型 weak_ptr 指针的内容。
reset()将当前 weak_ptr 指针置为空指针。
use_count()看指向和当前 weak_ptr 指针相同的 shared_ptr 指针的数量。
expired()判断当前 weak_ptr 指针为否过期(指针为空,或者指向的堆内存已经被释放)
lock()如果当前 weak_ptr 已经过期,则该函数会返回一个空的 shared_ptr 指针;反之,该函数返回一个和当前 weak_ptr 指向相同的 shared_ptr 指针。

再次强调,weak_ptr 模板类没有重载 * 和 -> 运算符,因此 weak_ptr 类型指针只能访问某一 shared_ptr
指针指向的堆内存空间,无法对其进行修改。

#include <iostream>
#include <memory>
using namespace std;
int main()
{
    std::shared_ptr<int> sp1(new int(10));
    std::shared_ptr<int> sp2(sp1);
    std::weak_ptr<int> wp(sp2);
    //输出和 wp 同指向的 shared_ptr 类型指针的数量
    cout << wp.use_count() << endl;
    //释放 sp2
    sp2.reset();
    cout << wp.use_count() << endl;
    //借助 lock() 函数,返回一个和 wp 同指向的 shared_ptr 类型指针,获取其存储的数据
    cout << *(wp.lock()) << endl;
    return 0;
}

在这里插入图片描述

1.3 unique_ptr

unique_ptr 是一个独享所有权的智能指针,同一时刻只能有一个指针指向同一个对象它提供了严格意义上的所有权。它取代了C++98中的auto_ptr。

unique_ptr对象包装一个原始指针,并负责其生命周期。当该对象被销毁时,会在其析构函数中删除关联的原始指针。

unique_ptr具有->和*运算符重载符,因此它可以像普通指针一样使用。

#include <iostream>
#include <memory>
using namespace std;

struct Task {
    int mId;
    Task(int id) :mId(id) {
        cout << "Task::Constructor" << endl;
    }
    ~Task() {
        cout << "Task::Destructor" << endl;
    }
};

int main()
{
    // 通过原始指针创建 unique_ptr 实例
    //shared_ptr<Task> taskPtr(new Task(23));
    //int id = taskPtr->mId;

    //调用其lock函数来获得shared_ptr示例,进而访问原始对象。
    //weak_ptr<Task> weak1 = taskPtr;
    //int id = weak1.lock()->mId;

    unique_ptr<Task> taskPtr(new Task(23));
    int id = taskPtr->mId;

    cout << id << endl;

    return 0;
}

在这里插入图片描述

不管函数正常退出还是异常退出(由于某些异常),也会始终调用taskPtr的析构函数。因此,原始指针将始终被删除并防止内存泄漏。

二、使用步骤

1.shared_ptr单独使用案例

两个shared_ptr指针互相引用导致的资源释放失败的例子:

代码如下(示例):

#include <iostream>
#include <memory>
#include <string>
using namespace std;

class B;
class A
{
public:
    shared_ptr<B> pb_;
    ~A()
    {
        cout << "A delete\n";
    }
};
class B
{
public:
    shared_ptr<A> pa_;
    ~B()
    {
        cout << "B delete\n";
    }
};

void fun() {
    shared_ptr<B> pb(new B());
    cout << "pb.use_count " << pb.use_count() << endl;//1
    shared_ptr<A> pa(new A());
    cout << "pa.use_count " << pa.use_count() << endl;//1

    pb->pa_ = pa;
    cout << "pb.use_count " << pb.use_count() << endl;//1
    cout << "pa.use_count " << pa.use_count() << endl;//2
    pa->pb_ = pb;
    //由于share_ptr是共享资源,所以pb所指向的资源的引用计数也会加1
    cout << "pb.use_count " << pb.use_count() << endl;//2
    cout << "pa.use_count " << pa.use_count() << endl;//2
}//程序结束时,没有调用A和B的析构函数

int main()
{
    fun();
    system("pause");
    return 0;
}

在这里插入图片描述

注意,不能用下面的方式使得两个 shared_ptr 对象托管同一个指针:

A* p = new A(10);

shared_ptr <A> sp1(p), sp2(p);

sp1 和 sp2 并不会共享同一个对 p 的托管计数,而是各自将对 p 的托管计数都记为 1(sp2 无法知道 p 已经被 sp1 托管过)。这样,当 sp1 消亡时要析构 p,sp2 消亡时要再次析构 p,这会导致程序崩溃。

2.weak_ptr和shared_ptr配合使用案例

代码如下(示例):

#include <iostream>
#include <memory>
#include <string>
using namespace std;

class B;
class A
{
public:
    weak_ptr<B> pb_weak;
    ~A()
    {
        cout << "A delete\n";
    }
};
class B
{
public:
    shared_ptr<A> pa_;
    ~B()
    {
        cout << "B delete\n";
    }
    void print() {
        cout << "This is B" << endl;
    }
};

void fun() {
    shared_ptr<B> pb(new B());
    cout << "pb.use_count " << pb.use_count() << endl;//1
    shared_ptr<A> pa(new A());
    cout << "pa.use_count " << pa.use_count() << endl;//1

    pb->pa_ = pa;
    cout << "pb.use_count " << pb.use_count() << endl;//1
    cout << "pa.use_count " << pa.use_count() << endl;//2

    pa->pb_weak = pb;
    cout << "pb.use_count " << pb.use_count() << endl;//1:弱引用不会增加所指资源的引用计数use_count()的值
    cout << "pa.use_count " << pa.use_count() << endl;//2

    shared_ptr<B> p = pa->pb_weak.lock();
    p->print();//不能通过weak_ptr直接访问对象的方法,须先转化为shared_ptr
    cout << "pb.use_count " << pb.use_count() << endl;//2
    cout << "pa.use_count " << pa.use_count() << endl;//2
}//函数结束时,调用A和B的析构函数

//资源B的引用计数一直就只有1,当pb析构时,B的计数减一,变为0,B得到释放,
//B释放的同时也会使A的计数减一,同时pa自己析构时也会使资源A的计数减一,那么A的计数为0,A得到释放。


int main()
{
    fun();
    system("pause");
    return 0;
}

在这里插入图片描述


3. unique_ptr和shared_ptr的区别

本质区别:

  • unique_ptr代表的是专属所有权,不支持复制和赋值。但是可以移动 shared_ptr 代表的是共享所有权,
  • shared_ptr是支持复制的和赋值以及移动的

资源消耗上:

  • unique_ptr 在默认情况下和裸指针的大小是一样的。所以 内存上没有任何的额外消耗,性能是最优的,我们大多数场景下用到的应该都是unique_ptr。
  • shared_ptr 的内存占用是裸指针的两倍。因为除了要管理一个裸指针外,还要维护一个引用计数。因此相比于 unique_ptr,shared_ptr 的内存占用更高。在使用 shared_ptr 之前应该考虑,是否真的需要使用 shared_ptr, 而非unique_ptr。

总结

  1. weak_ptr是一种用于解决shared_ptr相互引用时产生死锁问题的智能指针。如果有两个shared_ptr相互引用,那么这两个shared_ptr指针的引用计数永远不会下降为0,资源永远不会释放。weak_ptr是对对象的一种弱引用,它不会增加对象的use_count,weak_ptr和shared_ptr可以相互转化,shared_ptr可以直接赋值给weak_ptr,weak_ptr也可以通过调用lock函数来获得shared_ptr。
  2. weak_ptr指针通常不单独使用,只能和 shared_ptr类型指针搭配使用。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。即使有weak_ptr指向对象,对象也还是会被释放。
  3. weak_ptr并没有重载operator->和operator
    *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

暴躁茹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值