智能指针(weak_ptr )之三

1. std::weak_ptr

1.1 定义与用法

std::weak_ptr 是一种不拥有对象所有权的智能指针,用于观察但不影响对象的生命周期。主要用于解决 shared_ptr 之间的循环引用问题。

主要特性

  • 非拥有所有权:不增加引用计数。
  • 可从 shared_ptr 生成:通过 std::weak_ptr 可以访问 shared_ptr 管理的对象。
  • 避免循环引用:适用于双向关联或观察者模式。

1.2 避免循环引用

在存在双向关联(如父子关系)时,使用多个 shared_ptr 可能导致循环引用,导致内存泄漏。此时,可以使用 weak_ptr 来打破循环。

1.3 代码案例

场景:带有循环引用的观察者模式(天气预报系统)
#include 
#include 
#include 
#include 

// -------------------- 抽象接口部分 --------------------

// 观察者接口:所有 App 都要实现这个接口
class Observer {
public:
    virtual void update(float temp) = 0; // 被通知时调用
    virtual ~Observer() {
        std::cout << "Observer destroyed" << std::endl;
    }
};

// 主题(被观察者)接口:天气预报系统实现它
class Subject {
public:
    virtual void attach(std::shared_ptr<Observer> observer) = 0; // 添加观察者
    virtual void detach(std::shared_ptr<Observer> observer) = 0; // 移除观察者
    virtual void notify() = 0;                                   // 通知所有观察者
    virtual ~Subject() {
        std::cout << "Subject destroyed" << std::endl;
    }
};

// -------------------- 主题实现部分 --------------------

// 天气预报系统,持有观察者的 shared_ptr 列表
class WeatherStation : public Subject {
private:
    std::vector<std::shared_ptr<Observer>> observers; // 所有订阅者
    float temperature = 0.0f;

public:
    // 注册观察者
    void attach(std::shared_ptr<Observer> observer) override {
        observers.push_back(observer);
    }

    // 注销观察者
    void detach(std::shared_ptr<Observer> observer) override {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
    }

    // 通知所有观察者,调用它们的 update()
    void notify() override {
        for (auto& obs : observers) {
            obs->update(temperature);
        }
    }

    // 模拟温度变化,触发通知
    void setTemperature(float t) {
        temperature = t;
        std::cout << "[WeatherStation] New temperature: " << t << "°C\n";
        notify();
    }
    ~WeatherStation() {
        std::cout << "[WeatherStation] destroyed\n";
    }
};

// -------------------- 观察者实现部分 --------------------

// App 实现观察者接口:当温度太高,会自动注销
class AppClient : public Observer, public std::enable_shared_from_this<AppClient> {
private:
    std::shared_ptr<WeatherStation> station; // ❌ 持有 shared_ptr,形成循环引用

public:
    // 构造函数,记录主题指针
    AppClient(std::shared_ptr<WeatherStation> s) : station(s) {}

    // 被通知时调用
    void update(float temp) override {
        std::cout << "[AppClient] Received temperature: " << temp << "°C\n";

        // 如果温度过高,就自动注销
        if (temp > 35.0f) {
            std::cout << "[AppClient] Too hot! Unsubscribing...\n";

            // ❌ 使用 shared_from_this() 注销自己,导致循环引用无法释放
            station->detach(shared_from_this());
        }
    }

    // 析构函数
    ~AppClient() {
        std::cout << "[AppClient] destroyed\n";
    }
};

// -------------------- 主函数 --------------------

int main() {
    // 创建主题对象(天气系统)
    std::shared_ptr<WeatherStation> station = std::make_shared<WeatherStation>();

    // 创建观察者对象(App),并订阅
    std::shared_ptr<AppClient> app = std::make_shared<AppClient>(station);
    station->attach(app);

    // 第一次更新温度,通知观察者
    station->setTemperature(32.0f);

    // 第二次更新温度,观察者会自动注销
    // station->setTemperature(36.5f);  //如果解除这一步,那么不会产生引用循环

    // 程序即将结束
    std::cout << "[main] Exiting...\n";
    return 0;
}

输出

结果:产生引用循环Observer 和 Subject  没有销毁 导致内存泄漏

[WeatherStation] New temperature: 32°C
[AppClient] Received temperature: 32°C
[main] Exiting...

----------------------------------------------------------------------------
 //解除station->setTemperature(36.5f);				
结果:	不会产生引用循环

[WeatherStation] New temperature: 32°C  
[AppClient] Received temperature: 32°C  
[WeatherStation] New temperature: 36.5°C
[AppClient] Received temperature: 36.5°C
[AppClient] Too hot! Unsubscribing...   
[main] Exiting...
[AppClient] destroyed
Observer destroyed
[WeatherStation] destroyed
Subject destroyed

解决方案:使用 weak_ptr

改用 weak_ptr 其中一方,打破循环引用。

方式一 把AppClient里面的station改为weak_ptr类型,其他操作也做出相应改变

//方式一 把AppClient里面的station改为weak_ptr类型,其他操作也做出相应改变
#include 
#include 
#include 
#include 

// -------------------- 抽象接口部分 --------------------

// 观察者接口:所有 App 都要实现这个接口
class Observer {
public:
    virtual void update(float temp) = 0; // 被通知时调用
    virtual ~Observer() {
        std::cout << "Observer destroyed" << std::endl;
    }
};

// 主题(被观察者)接口:天气预报系统实现它
class Subject {
public:
    virtual void attach(std::shared_ptr<Observer> observer) = 0; // 添加观察者
    virtual void detach(std::shared_ptr<Observer> observer) = 0; // 移除观察者
    virtual void notify() = 0;                                   // 通知所有观察者
    virtual ~Subject() {
        std::cout << "Subject destroyed" << std::endl;
    }
};

// -------------------- 主题实现部分 --------------------

// 天气预报系统,持有观察者的 shared_ptr 列表
class WeatherStation : public Subject {
private:
    std::vector<std::shared_ptr<Observer>> observers; // 所有订阅者
    float temperature = 0.0f;

public:
    // 注册观察者
    void attach(std::shared_ptr<Observer> observer) override {
        observers.push_back(observer);
    }

    // 注销观察者
    void detach(std::shared_ptr<Observer> observer) override {
        observers.erase(std::remove(observers.begin(), observers.end(), observer), observers.end());
    }

    // 通知所有观察者,调用它们的 update()
    void notify() override {
        for (auto& obs : observers) {
            obs->update(temperature);
        }
    }

    // 模拟温度变化,触发通知
    void setTemperature(float t) {
        temperature = t;
        std::cout << "[WeatherStation] New temperature: " << t << "°C\n";
        notify();
    }
    ~WeatherStation() {
        std::cout << "[WeatherStation] destroyed\n";
    }
};

// -------------------- 观察者实现部分 --------------------

// App 实现观察者接口:当温度太高,会自动注销
class AppClient : public Observer, public std::enable_shared_from_this<AppClient> {
private:
    std::weak_ptr<WeatherStation> station;  //修改1

public:
    // 构造函数,记录主题指针
    AppClient(std::shared_ptr<WeatherStation> s) : station(s) {}

    // 被通知时调用
    void update(float temp) override {
        std::cout << "[AppClient] Received temperature: " << temp << "°C\n";

        // 如果温度过高,就自动注销
        if (temp > 35.0f) {
            std::cout << "[AppClient] Too hot! Unsubscribing...\n";

            if(!station.expired()) //修改2
            {
              station.lock()->detach(shared_from_this()); 
            }
        }
    }

    // 析构函数
    ~AppClient() {
        std::cout << "[AppClient] destroyed\n";
    }
};

// -------------------- 主函数 --------------------

int main() {
    // 创建主题对象(天气系统)
    std::shared_ptr<WeatherStation> station = std::make_shared<WeatherStation>();

    // 创建观察者对象(App),并订阅
    std::shared_ptr<AppClient> app = std::make_shared<AppClient>(station);
    station->attach(app);

    // 第一次更新温度,通知观察者
    station->setTemperature(32.0f);

    // 第二次更新温度,观察者会自动注销
    // station->setTemperature(36.5f);  //如果解除这一步,那么不会产生引用循环

    // 程序即将结束
    std::cout << "[main] Exiting...\n";
    return 0;
}

输出

[WeatherStation] New temperature: 32°C
[AppClient] Received temperature: 32°C
[main] Exiting...
[WeatherStation] destroyed
[AppClient] destroyed
Observer destroyed
Subject destroyed

方式二 把WeatherStation里面的observers改为weak_ptr类型,其他操作也做出相应改变

#include 
#include 
#include 
#include 

// -------------------- 抽象接口部分 --------------------

// 观察者接口:所有 App 都要实现这个接口
class Observer
{
public:
  virtual void update(float temp) = 0; // 被通知时调用
  virtual ~Observer()
  {
    std::cout << "Observer destroyed" << std::endl;
  }
};

// 主题(被观察者)接口:天气预报系统实现它
class Subject
{
public:
  virtual void attach(std::shared_ptr<Observer> observer) = 0; // 添加观察者
  virtual void detach(std::shared_ptr<Observer> observer) = 0; // 移除观察者
  virtual void notify() = 0;                                   // 通知所有观察者
  virtual ~Subject()
  {
    std::cout << "Subject destroyed" << std::endl;
  }
};

// -------------------- 主题实现部分 --------------------

// 天气预报系统,持有观察者的 shared_ptr 列表
class WeatherStation : public Subject
{
private:
  std::vector<std::weak_ptr<Observer>> observers; // 修改1
  float temperature = 0.0f;

public:
  // 注册观察者
  void attach(std::shared_ptr<Observer> observer) override
  {
    observers.push_back(observer);
  }

  // 注销观察者
  void detach(std::shared_ptr<Observer> observer) override
  {
    observers.erase(    // 修改2 
        std::remove_if(observers.begin(), observers.end(),
                       [&observer](const std::weak_ptr<Observer> &wptr)
                       {
                         return !wptr.expired() && wptr.lock() == observer;
                       }),
        observers.end());
  }


  // void detach(std::shared_ptr observer) override {
  //        std::cout << "[WeatherStation] detach\n";
  //        std::weak_ptr temp = observer;
  //        observers.erase(std::remove(observers.begin(), observers.end(), temp), observers.end());
  //   }
  //如果改成这样的话,思路很接近核心,看起来像是合理的,但它其实 仍然不行
  //原因就在于:std::weak_ptr 不支持 == 比较(哪怕是彼此之间),除非你先把它们 lock 成 shared_ptr
  //两个 weak_ptr 即使指向相同的对象,它们也可能过期(expired)
  //无法确定“空的”和“已销毁”的哪个该算“相等”



  // 通知所有观察者,调用它们的 update()
  void notify() override
  {
    for (auto &obs : observers)
    {
      if (!obs.expired()) // 修改3
        obs.lock()->update(temperature); 
    }
  }

  // 模拟温度变化,触发通知
  void setTemperature(float t)
  {
    temperature = t;
    std::cout << "[WeatherStation] New temperature: " << t << "°C\n";
    notify();
  }
  ~WeatherStation()
  {
    std::cout << "[WeatherStation] destroyed\n";
  }
};

// -------------------- 观察者实现部分 --------------------

// App 实现观察者接口:当温度太高,会自动注销
class AppClient : public Observer, public std::enable_shared_from_this<AppClient>
{
private:
  std::shared_ptr<WeatherStation> station; // ❌ 持有 shared_ptr,形成循环引用

public:
  // 构造函数,记录主题指针
  AppClient(std::shared_ptr<WeatherStation> s) : station(s) {}

  // 被通知时调用
  void update(float temp) override
  {
    std::cout << "[AppClient] Received temperature: " << temp << "°C\n";

    // 如果温度过高,就自动注销
    if (temp > 35.0f)
    {
      std::cout << "[AppClient] Too hot! Unsubscribing...\n";

      // ❌ 使用 shared_from_this() 注销自己,导致循环引用无法释放
      station->detach(shared_from_this());
    }
  }

  // 析构函数
  ~AppClient()
  {
    std::cout << "[AppClient] destroyed\n";
  }
};

// -------------------- 主函数 --------------------

int main()
{
  // 创建主题对象(天气系统)
  std::shared_ptr<WeatherStation> station = std::make_shared<WeatherStation>();

  // 创建观察者对象(App),并订阅
  std::shared_ptr<AppClient> app = std::make_shared<AppClient>(station);
  station->attach(app);

  // 第一次更新温度,通知观察者
  station->setTemperature(32.0f);

  // 第二次更新温度,观察者会自动注销
  // station->setTemperature(36.5f);  //如果解除这一步,那么不会产生引用循环

  // 程序即将结束
  std::cout << "[main] Exiting...\n";
  return 0;
}

输出

[WeatherStation] New temperature: 32°C
[AppClient] Received temperature: 32°C
[main] Exiting...
[AppClient] destroyed
Observer destroyed
[WeatherStation] destroyed
Subject destroyed

1.4 访问 weak_ptr 指向的对象

weak_ptr 不能直接访问对象,需要通过 lock() 方法转换为 shared_ptr,并检查对象是否仍然存在。

  • std::weak_ptr::expired()
    • 功能:std::weak_ptr 的 expired() 成员函数用于检查 std::weak_ptr 所引用的对象是否已经被销毁(即关联的 std::shared_ptr 的引用计数是否已变为 0)。
    • 返回值:
      • bool类型
        • true:表示 std::weak_ptr 已过期(引用的对象已被销毁)。
        • false:表示 std::weak_ptr 未过期(引用的对象仍然存在)。
#include 
#include 

int main() {
    std::shared_ptr<int> sp = std::make_shared<int>(42);
    std::weak_ptr<int> wp = sp;

    if (auto locked = wp.lock()) { //wp.lock() 会尝试安全地获取一个新的 shared_ptr,如果资源还存在,它就返回一个有效的 								 //shared_ptr,否则返回一个空的 shared_ptr 到时if(shared_ptr)会触发operator bool()函								 //	数,也就是if(shared_ptr.bool()) == if(return shared_ptr.get() != nullptr;)
        std::cout << "Value: " << *locked << std::endl;
    } else {
        std::cout << "Object no longer exists." << std::endl;
    }

    sp.reset(); // 释放资源

    if (auto locked = wp.lock()) { // 再次尝试获取 shared_ptr 
        std::cout << "Value: " << *locked << std::endl;
    } else {
        std::cout << "Object no longer exists." << std::endl;
    }
    /*	效果一样!expired()
    if(!wp.expired()) { // 再次尝试获取 shared_ptr 
        std::cout << "Value: " << *wp.lock() << std::endl;
    } else {
        std::cout << "Object no longer exists." << std::endl;
    }
    */

    return 0;
}

输出

Value: 42
Object no longer exists.

解析

  • wp.lock() 返回一个 shared_ptr,如果对象依然存在,则有效。
  • sp.reset() 释放资源后,wp.lock() 无法获取有效的 shared_ptr

你可能感兴趣的:(C++,c++)