C++11之智能指针shared_ptr

发布时间 2023-03-27 19:42:45作者: LoneWalker_01

  在 C++ 开发中,我们经常会遇到程序运行中突然崩溃、程序运行所用内存越来越多最终不得不重启等问题,这些问题往往都是内存资源管理不当造成的。C++11 新标准中,增添了 unique_ptr、shared_ptr 以及 weak_ptr 这 3 个智能指针来实现堆内存的自动回收,今天就简单的介绍一下shared_ptr的使用。

  C++ 智能指针底层是采用引用计数的方式实现的。智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。当堆空间对应的整形值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉。多个 shared_ptr 智能指针可以共同使用同一块堆内存。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)。

  从名字share就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。当我们调用release()时,当前指针会释放资源所有权,计数减一。当计数等于0时,资源会被释放。

一、智能指针shared_ptr<T>的初始化

#ifndef SHAREPTR_H
#define SHAREPTR_H

#include <QObject>
#include <QDebug>

class SharePtr : public QObject
{
    Q_OBJECT
public:
    explicit SharePtr(QString info, QObject *parent = nullptr);
    ~SharePtr();

public:
    QString& get_info();
    void set_info(QString info);
    void print_info();

private:
    QString m_info;
};

#endif // SHAREPTR_H
#include "share_ptr.h"

SharePtr::SharePtr(QString info, QObject *parent) : QObject(parent),m_info(info)
{
    QString test = QString("%1被创造").arg(info);
    qDebug() << test;
}


SharePtr::~SharePtr()
{
    qDebug() << "SharePtr delete:" << m_info;
}

QString& SharePtr::get_info()
{
    return m_info;
}

void SharePtr::set_info(QString info)
{
    m_info = info;
}

void SharePtr::print_info()
{
    qDebug() << "info:" << m_info;
}
#include <QCoreApplication>
#include <QDebug>

#include "share_ptr.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    /* 通过如下2种方式,可以构造出 shared_ptr<T> 类型的空智能指针 */
    std::shared_ptr<SharePtr> p1;             /* 不传入任何实参 */
    /* 注意,空的shared_ptr指针,其初始引用计数为 0,而不是 1。 */
    qDebug() << "p1.count:" << p1.use_count();
    std::shared_ptr<SharePtr> p2(nullptr);    /* 传入空指针 nullptr */
    qDebug() << "p2.count:" << p2.use_count();

    /* C++11 标准中提供了 std::make_shared<T> 模板函数,其可以用于初始化 shared_ptr智能指针 */
    std::shared_ptr<SharePtr> share_ptr = std::make_shared<SharePtr>("123");
    /* 查看资源的所有者个数 */
    qDebug() << "share_ptr.count:" << share_ptr.use_count();
    share_ptr.get()->print_info();

    /* 在构建 shared_ptr智能指针,也可以明确其指向 */
    std::shared_ptr<SharePtr> share_ptr1(new SharePtr("456"));
    qDebug() << "share_ptr1.count:" << share_ptr1.use_count();
    share_ptr1.get()->print_info();

    return a.exec();
}

打印结果如下:

 二、shared_ptr<T> 模板还提供有相应的拷贝构造函数和移动构造函数

#include <QCoreApplication>
#include <QDebug>

#include "share_ptr.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    /* 调用拷贝构造函数 */
    /* p 和 p1 都是 shared_ptr 类型的智能指针,因此可以用 p 来初始化 p1,由于 p3 是左值,
     *因此会调用拷贝构造函数。需要注意的是,如果 p 为空智能指针,则 p1 也为空智能指针,其引用计数初始值为 0;反之,
     * 则表明 p 和 p1 指向同一块堆内存,同时该堆空间的引用计数会加 1。 */
    std::shared_ptr<SharePtr> p = std::make_shared<SharePtr>("111");
    qDebug() << "p.count:" << p.use_count();
    std::shared_ptr<SharePtr> p1(p); /* 或者 std::shared_ptr<SharePtr> p1 = p; */
    qDebug() << "p1.count:" << p1.use_count();

    /* 调用移动构造函数  */
    std::shared_ptr<SharePtr> p2(std::move(p1)); /* 或者 std::shared_ptr<SharePtr> p2 = std::move(p1); */
    /* 对于 std::move(p1) 来说,该函数会强制将 p1 转换成对应的右值,因此初始化 p2 调用的是移动构造函数。
     * 另外和调用拷贝构造函数不同,用 std::move(p1) 初始化 p2,会使得 p2 拥有了 p1 的堆内存,而 p1 则变成了空智能指针*/
    qDebug() << "p2.count:" << p2.use_count();
    qDebug() << "p1.count:" << p1.use_count();

    std::shared_ptr<SharePtr> p3 = std::make_shared<SharePtr>("222");
    qDebug() << "p3.count():" << p3.use_count();
    std::shared_ptr<SharePtr> p4 = std::make_shared<SharePtr>("333");
    qDebug() << "p4.count():" << p4.use_count();

    p3 = p4; /* "333"引用计数加1,"222"销毁 */
    p3.get()->print_info();
    qDebug() << "p3.count():" << p3.use_count();
    qDebug() << "p4.count():" << p4.use_count();
    p3.reset();
    qDebug() << "p3.count():" << p3.use_count();
    p4.reset();/* 引用计数为0,此时"333"销毁 */

    return a.exec();
}

 打印结果如下:

 三、自定义智能指针的释放规则

  在初始化 shared_ptr 智能指针时,还可以自定义所指堆内存的释放规则,这样当堆内存的引用计数为 0 时,会优先调用我们自定义的释放规则。在某些场景中,自定义释放规则是很有必要的。

比如,对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存。

对于申请的动态数组,释放规则可以使用 C++11 标准中提供的 default_delete<T> 模板类,我们也可以自定义释放规则: