大话C++:第30篇 破解shared_ptr引发的循环引用问题

正式介绍循环引用问题之前,我们必须先要掌握weak_ptr。

1 std::weak_ptr基本概念

std::weak_ptr是C++标准库中的一种智能指针,它是为了解决std::shared_ptr可能产生的循环引用问题而设计的。与std::shared_ptr不同,std::weak_ptr对对象的引用不会增加对象的引用计数,也不会影响对象的生命周期。这意味着,当最后一个std::shared_ptr指向的对象被销毁时,任何指向该对象的std::weak_ptr都会自动变为空。

std::weak_ptr 的主要操作和成员函数:

操作/函数描述
构造函数
weak_ptr()构造一个空的 weak_ptr,不指向任何对象。
weak_ptr(const weak_ptr& other)拷贝构造函数,构造一个新的 weak_ptr,与 other 共享对象所有权状态。
weak_ptr(const shared_ptr<T>& sp)shared_ptr 构造 weak_ptr,共享对象所有权状态但不增加引用计数。
成员函数
shared_ptr<T> lock() const尝试获取所指向对象的 shared_ptr。如果对象仍然存在,则返回一个有效的 shared_ptr;否则返回一个空的 shared_ptr
bool expired() const检查 weak_ptr 是否已经过期(即它指向的对象是否已经被销毁)。如果已过期,则返回 true;否则返回 false
long use_count() const返回与 weak_ptr 共享对象所有权的 shared_ptr 的数量(即引用计数)。注意:这个函数在标准库中并不存在,但经常被提及作为一个假设性或扩展性的功能。实际上,标准库并没有提供直接获取引用计数的函数。
void reset()重置 weak_ptr,使其变为空,不再指向任何对象。
void swap(weak_ptr& other)交换两个 weak_ptr 对象的所有权状态。
操作符
weak_ptr& operator=(const weak_ptr& other)赋值操作符,将 other 的所有权状态赋给当前 weak_ptr
weak_ptr& operator=(const shared_ptr<T>& sp)赋值操作符,从 shared_ptr 赋值,共享对象所有权状态但不增加引用计数。

注意

  • use_count() 函数在 C++ 标准库中并不存在。这是因为直接暴露引用计数可能会导致不安全的代码实践。通常,使用 expired()lock() 函数来检查对象是否存在是更安全、更推荐的方式。

  • weak_ptr 没有提供直接访问所指向对象的成员函数或数据成员的操作符(如 ->*)。要访问对象,必须先通过 lock() 获取一个有效的 shared_ptr,然后使用该 shared_ptr 进行访问。如果对象已经不存在,lock() 将返回一个空的 shared_ptr,访问将无法进行。

std::weak_ptr 基本用法,代码示例

#include <iostream>
#include <memory>

class Example 
{
public:
    ~Example() 
    {
        std::cout << "Example::~Example() called." << std::endl;
    }
};

int main() 
{
    std::shared_ptr<Example> sp1 = std::make_shared<Example>(); // 创建一个 shared_ptr.

    {
        // 从 sp1 创建一个 weak_ptr.
        std::weak_ptr<Example> wp = sp1; 
        std::cout << "wp points to " << (wp.expired() ? "null" : "an object") << std::endl;

        // 通过 weak_ptr 访问对象.
        // 必须先将其转换为 shared_ptr. 如果原 shared_ptr 不再存在,
        // 那么这个调用将返回一个新的空的 shared_ptr.
        if (auto sp2 = wp.lock()) 
        {
            std::cout << "sp2 points to " << (sp2.get() ? "an object" : "null") << std::endl;
        }
        else 
        {
            std::cout << "sp2 is null" << std::endl;
        }
    }

    std::cout << "sp1 points to " << (sp1.get() ? "an object" : "null") << std::endl;
    std::cout << "sp1 use_count() = " << sp1.use_count() << std::endl;

    return 0;
}

综合案例:实现了一个简单的观察者模式。

#include <iostream>
#include <list>
#include <memory>

// 观察者接口
class Observer 
{
public:
    virtual void update() = 0;
    virtual ~Observer() = default;
};

// 具体观察者
class ConcreteObserver : public Observer 
{
public:
    void update() override 
    {
        std::cout << "ConcreteObserver::update() called" << std::endl;
    }
};

// 主题接口
class Subject 
{
public:
    virtual void registerObserver(std::shared_ptr<Observer> observer) = 0;
    virtual void removeObserver(std::shared_ptr<Observer> observer) = 0;
    virtual void notifyObservers() = 0;
    virtual ~Subject() = default;
};

// 具体主题
class ConcreteSubject : public Subject 
{
private:
    std::list<std::weak_ptr<Observer>> observers;

public:
    void registerObserver(std::shared_ptr<Observer> observer) override 
    {
        observers.push_back(observer);
    }

    void removeObserver(std::shared_ptr<Observer> observer) override 
    {
        observers.remove_if([observer](const std::weak_ptr<Observer>& wp) {
            return !wp.expired() && wp.lock() == observer;
        });
    }

    void notifyObservers() override {
        for (auto it = observers.begin(); it != observers.end();) 
        {
            if (auto observer = it->lock()) 
            {
                observer->update();
                ++it;
            } else 
            {
                it = observers.erase(it);
            }
        }
    }
};

int main() 
{
    std::shared_ptr<ConcreteSubject> subject = std::make_shared<ConcreteSubject>();
    std::shared_ptr<Observer> observer1 = std::make_shared<ConcreteObserver>();
    std::shared_ptr<Observer> observer2 = std::make_shared<ConcreteObserver>();

    subject->registerObserver(observer1);
    subject->registerObserver(observer2);

    subject->notifyObservers();

    // 释放 observer1,但不影响 subject 和 observer2
    observer1.reset();

    subject->notifyObservers(); // observer1 不再接收通知

    return 0;
}

2 循环引用问题

循环引用问题出现在两个或多个对象相互使用std::shared_ptr来引用对方时。这种情况下,每个对象的引用计数都至少为1(因为被另一个对象引用),所以即使外部的所有std::shared_ptr都不再指向这些对象,它们的引用计数也永远不会下降到0。这意味着这些对象将永远不会被销毁,从而导致内存泄漏。

例如,考虑两个类A和B,它们各自包含一个指向对方的std::shared_ptr成员。如果A的一个实例引用B的一个实例,而B的那个实例又引用A的那个实例,就形成了一个循环引用。即使没有其他代码引用这两个对象,它们的引用计数也永远不会是0,因此它们所占用的内存将不会被释放。

为了解决这个问题,C++提供了std::weak_ptrstd::weak_ptr是一种不增加引用计数的智能指针,它允许你观察(但不能控制)一个由std::shared_ptr管理的对象的生命周期。当最后一个std::shared_ptr被销毁时,对象将被删除,而任何指向该对象的std::weak_ptr都将自动变为空。通过使用std::weak_ptr来替代其中一个方向上的std::shared_ptr,可以打破循环引用,从而允许对象在不再需要时被正确销毁。

#include <iostream>
#include <memory>
#include <string>

// 前向声明
class Child;

class Parent
{
public:
    std::shared_ptr<Child> m_child_ptr; // 指向Child对象指针

    // 析构函数
    ~Parent()
    {
        std::cout << "调用Parent析构函数" << std::endl;
    }
};


class Child
{
public:
    std::shared_ptr<Parent> m_parent_ptr; // 指向父类对象指针

    // 析构函数
    ~Child()
    {
        std::cout << "调用Child析构函数" << std::endl;
    }
};



int main()
{
    // 创建Parent和Child对象指针
    std::shared_ptr<Parent> psptr = std::make_shared<Parent>();
    std::shared_ptr<Child> csptr = std::make_shared<Child>();

    // 核心目的使其成环
    // 设置父类对象里的child指针指向子类
    psptr->m_child_ptr = csptr;
    // 设置子类对象里的parent指针指向父类
    csptr->m_parent_ptr = psptr;

    std::cout << "超出作用域,注意看各个对象的析构函数打印信息" << std::endl;

    return 0;
}

 解决方案,至少一个对象采用std::weak_ptr

#include <iostream>
#include <memory>
#include <string>

// 前向声明
class Child;

class Parent
{
public:
    std::weak_ptr<Child> m_child_ptr; // 指向Child对象指针

    // 析构函数
    ~Parent()
    {
        std::cout << "调用Parent析构函数" << std::endl;
    }
};


class Child
{
public:
    std::weak_ptr<Parent> m_parent_ptr; // 指向父类对象指针

    // 析构函数
    ~Child()
    {
        std::cout << "调用Child析构函数" << std::endl;
    }
};


int main()
{
    // 创建Parent和Child对象指针
    std::shared_ptr<Parent> psptr = std::make_shared<Parent>();
    std::shared_ptr<Child> csptr = std::make_shared<Child>();

    // 核心目的使其成环
    // 设置父类对象里的child指针指向子类
    psptr->m_child_ptr = csptr;
    // 设置子类对象里的parent指针指向父类
    csptr->m_parent_ptr = psptr;

    std::cout << "超出作用域,注意看各个对象的析构函数打印信息" << std::endl;

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值