前一段時間遇到一個問題,做的一個buff管理器,buff執行過程中需要用到釋放skill的對象,當時為了友善和執行效率,直接儲存了對象的指針(沒有用智能指針)。後來業務邏輯修改了,在buff存在期間,釋放skill對象可能已經銷毀了。這就尴尬了,buff執行過程中可能會用到已經銷毀的對象,但是buff管理器不知道,這樣會導緻未知的錯誤。
後來處理方法是buff對象直接儲存釋放skill對象的ID,每次執行的時候,從map中取一下,如果可以取到就是表明對象還活着,取不到表示對象已經死了。這種方式我不是很喜歡,還有更好的方式就是用智能指針shared_ptr與weak_ptr配合使用。
不同于shared_ptr通過引用計數來控制對象的銷毀,想鐵絲一樣,綁在對象上,每次指派給shared_ptr相當于多一層綁定(shared_ptr的引用計數加一),當綁定對象的所有shared_ptr都銷毀時(引用計數減到零),自動銷毀對象。而weak_ptr像是棉線綁在對象上,不會影響綁定對象。使用時要先轉化成shared_ptr才能調用對象。
weak_ptr看名字就是比較弱的指針,影響也弱,指派不會導緻引用計數加一,它用來輔助shared_ptr的。weak_ptr可以通過一個shared_ptr或者另一個weak_ptr對象構造,進而獲得對象的觀察權。但它的構造不會引起指針引用計數的增加。
shared_ptr<T> sp(new T()); // sp 指向T對象shared_ptr引用計數加1
weak_ptr<T> w(sp); //與shared_ptr指向相同的對象,shared_ptr引用計數不變。
w=p; //p可以是shared_ptr或weak_ptr
w.reset(); //将w置空。
w.use_count(); //傳回與w共享對象的shared_ptr的個數。
w.lock(); //如果use_count()為0,傳回一個空shared_ptr,否則傳回非空shared_ptr。
下面我寫了一個例子用weak_ptr和shared_ptr來處理,當對象銷毀後,我們再通過指針使用對象時可以知道。
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <vector>
#include <memory> // shared_ptr weak_ptr
using namespace std;
class obj
{
public:
obj(int _index) :index(_index)
{};
void update()
{
cout << ++index << endl;
}
private:
int index;
};
class objable
{
public:
void regist(weak_ptr<obj> pObj);
void notify();
public:
vector<weak_ptr<obj> > objs;
};
void objable::regist(weak_ptr<obj> pObj)
{
objs.push_back(pObj);
}
void objable::notify()
{
for (vector<weak_ptr<obj> >::iterator it = objs.begin(); it != objs.end();)
{
shared_ptr<obj> pObj(it->lock());
if (pObj)
{
pObj->update();
++it;
}
else
{
it = objs.erase(it);
}
}
}
int main()
{
objable ab;
//shared_ptr 引用計數為1
shared_ptr<obj> obj1(new obj(1));
ab.regist(obj1);
ab.notify();
{
shared_ptr<obj> obj1(new obj(10));
ab.regist(obj1);
ab.notify();
}//出來大括号obj1銷毀 智能指針引用計數減1 此時引用計數為0 對象obj(10)銷毀
ab.notify();
cin.get();
}
執行結果:
在陳碩大神在《Linux多線程服務端程式設計:使用muduo C++網絡庫》一書中處理觀察者模式用了相似的方式處理。
因為邏輯都在一個主線程中執行,不涉及race conditions,也可以這麼處理:
class obj
{
public:
obj(int _index) :index(_index)
{
};
void init(objable* s)
{
s->regist(this);
}
void update()
{
cout << ++index << endl;
}
~obj()
{
s->unregist(this);
}
private:
int index;
};
第三種方法是引入二級指針:
如果對象proxy指針永遠不會被删除,當Object對象删除時,proxy指針變為null
這樣就p2就不會變成空懸指針了