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 <iostream>
#include <vector>
#include <memory>
#include <algorithm>// -------------------- 抽象接口部分 --------------------// 觀察者接口:所有 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 <iostream>
#include <vector>
#include <memory>
#include <algorithm>// -------------------- 抽象接口部分 --------------------// 觀察者接口:所有 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; //修改1public:// 構造函數,記錄主題指針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 <iostream>
#include <vector>
#include <memory>
#include <algorithm>// -------------------- 抽象接口部分 --------------------// 觀察者接口:所有 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; // 修改1float 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> observer) override {// std::cout << "[WeatherStation] detach\n";// std::weak_ptr<Observer> 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()) // 修改3obs.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 未過期(引用的對象仍然存在)。
- bool類型
#include <iostream>
#include <memory>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
。