C++11之智能指针weak_ptr

发布时间 2023-03-29 18:36:19作者: 青衣守旧人

  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<T> 模板类中没有重载 * 和 -> 运算符,这也就意味着,weak_ptr 类型指针只能访问所指的堆内存,而无法修改它。

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

#include <QCoreApplication>
#include <memory>

#include "smart_pointer.h"

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

    /* 成员方法                           功 能
       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 指针。
    */

    std::shared_ptr<SmartPointer> p = std::make_shared<SmartPointer>("test");
    std::shared_ptr<SmartPointer> p1(p);
    /* 并不影响shared_ptr的引用计数 */
    std::weak_ptr<SmartPointer> p2 = p1;

    qDebug() << "p1.use_count:" << p1.use_count();

    if (p2.lock() != nullptr) {
        qDebug() << (p2.lock())->get_info();
    }

    qDebug() << p2.expired();

    p2.reset();
    qDebug() << "p2.use_count:" << p2.use_count();

    qDebug() << p2.expired();

    if (p2.lock() == nullptr) {
        qDebug() << "weak_ptr 已经过期";
    }

    return a.exec();
}

打印结果如下:

 二、weak_ptr解决shared_ptr循环引用的问题

定义两个类,每个类中又包含一个指向对方类型的智能指针作为成员变量,然后创建对象,设置完成后查看引用计数后退出

class TestB;
class TestA
{
public:
    TestA() { qDebug() << "---TestA()---"; }
    ~TestA() { qDebug() << "---~TestA()---";}
    void set_ptr(std::shared_ptr<TestB> &ptr) {m_ptr_b = ptr;}
    void a_use_count() { qDebug() << "a use count : " << m_ptr_b.use_count(); }
    void print_inro() { qDebug() << "this is class TestA!"; }
private:
    std::shared_ptr<TestB> m_ptr_b;
};

class TestB
{
public:
    TestB() { qDebug() << "---TestB()---"; }
    ~TestB() { qDebug() << "---~TestB()---";}
    void set_ptr(std::shared_ptr<TestA> &ptr) {m_ptr_a = ptr;}
    void a_use_count() { qDebug() << "a use count : " << m_ptr_a.use_count(); }
    void print_inro() { qDebug() << "this is class TestB!"; }
private:
    std::shared_ptr<TestA> m_ptr_a;
};
void test()
{
    std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();
    std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();

    qDebug() << "a use count : " << ptr_a.use_count();
    qDebug() << "b use count : " << ptr_b.use_count();

    ptr_a->set_ptr(ptr_b);
    ptr_b->set_ptr(ptr_a);

    qDebug() << "a use count : " << ptr_a.use_count();
    qDebug() << "b use count : " << ptr_b.use_count();
}

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

    test();

    return a.exec();
}

打印结果:

通过结果可以看到,最后TestA和TestB的对象并没有被析构,其中的引用效果如下图所示,起初定义完ptr_a和ptr_b时,只有1、2两条引用,然后调用函数set_ptr后又增加了3、4两条引用,当test()这个函数返回时,对象ptr_a和ptr_b被销毁,也就是1、3两条引用会被断开,但是2、4两条引用依然存在,每一个的引用计数都不为0,结果就导致其指向的内部对象无法析构,造成内存泄漏。

 

 解决这种状况的办法就是将两个类中的一个成员变量改为weak_ptr对象,因为weak_ptr不会增加引用计数,使得引用形不成环,最后就可以正常的释放内部的对象,不会造成内存泄漏,比如将TestB中的成员变量改为weak_ptr对象,代码如下:

class TestB
{
public:
    TestB() { qDebug() << "---TestB()---"; }
    ~TestB() { qDebug() << "---~TestB()---";}
    void set_ptr(std::shared_ptr<TestA> &ptr) {m_ptr_a = ptr;}
    void a_use_count() { qDebug() << "a use count : " << m_ptr_a.use_count(); }
    void print_inro() { qDebug() << "this is class TestB!"; }
private:
    std::weak_ptr<TestA> m_ptr_a;
};

打印结果如下:

 

 通过这次结果可以看到,TestA和TestB的对象都被正常的析构了,引用关系如下图所示,流程与上一例子相似,但是不同的是4这条引用是通过weak_ptr建立的,并不会增加引用计数,也就是说TestA的对象只有一个引用计数,而TestB的对象只有2个引用计数,当test()这个函数返回时,对象ptr_a和ptr_b被销毁,也就是1、3两条引用会被断开,此时TestA对象的引用计数会减为0,对象被销毁,其内部的m_ptr_b成员变量也会被析构,导致TestB对象的引用计数会减为0,对象被销毁,进而解决了引用成环的问题。