看了書之後豁然開朗啊,之前還真是個半調子。總結一下使用情境。
從失敗的例子講起 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 struct Obj { string name_; Obj(const string& name) :name(name_) {} ~Obj() {} }; class ObjFactory { map<string, shared_ptr<Obj>> lookup_; public: shared_ptr<Obj> get(const string &name) { auto it = lookup_.find(name); if (it != lookup_.end()) return it->second; lookup_[name] = shared_ptr<Obj>(new Obj(name)); return lookup_[name]; } };
這段程式碼最大的問題就是,如果ObjFactory的instance沒被摧毀,所有拿到的Obj都不會被釋放。
用 weak_ptr 取代 shared_ptr 在ObjectFactory的部份不要保存shared_ptr,這樣會增加reference count,用weak_ptr取而代之。需要的話再promotion成shared_ptr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class ObjFactory { map<string, weak_ptr<Obj>> lookup_; public: shared_ptr<Obj> get(const string &name) { auto it = lookup_.find(name); if (it != lookup_.end()) { shared_ptr<Obj> obj((it->second).lock()); return obj; } shared_ptr<Obj> instance(new Obj(name)); weak_ptr<Obj> obj(instance); lookup_[name] = obj; return instance; } };
看起來沒什麼問題,不過譽到以下這種情況就爛了
1 2 3 4 5 6 7 8 9 10 for (int i = 0; i < 3; i++) { shared_ptr<Obj> s = pFactory->get("HM"); if (s) { cout << "create new obj" << endl; } else { cout << "cannot create new obj" << endl; } }
我們希望看到的是每次都能拿到一個新物件,結果發現只有第一次能成功。原因出在當Obj被摧毀的時候,沒有順便清理掉ObjFactory 當中的lookup_的資料,以致於下一次使用的時候,可以找到上一次殘留的屍體,promtion之後就是一個空的shared_ptr。 解決方法就是使用shared_ptr時,同時自訂一個destructor,除了釋放memory之外,也把map裡面的資料輕空。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 class ObjFactory { map<string, weak_ptr<Obj>> lookup_; void deleteObj(Obj *pObj) { lookup_.erase(pObj->name_); delete pObj; } public: shared_ptr<Obj> get(const string &name) { auto it = lookup_.find(name); if (it != lookup_.end()) { shared_ptr<Obj> obj((it->second).lock()); return obj; } shared_ptr<Obj> instance(new Obj(name), bind(&ObjFactory::deleteObj, this, placeholders::_1)); weak_ptr<Obj> obj(instance); lookup_[name] = obj; return instance; } };
現在看起來正常了,不過街下來這個Case又會遇到問題。
1 2 3 4 { shared_ptr<Obj> obj1 = pFactory->get("Obj1"); delete pFactory; }
由於在這個block之內,pFactory已經被釋放了,所以那個destructor的this一點都不可靠,因此要把裡面那個this
轉成一個shared_ptr。
enable_shared_from_this enable_shared_from_this就是因此登場的,他可以把this指標所在的位置轉成一個shared_ptr。 而原先的測試部份也必須用shared_ptr管理了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class ObjFactory : public enable_shared_from_this<ObjFactory> { map<string, weak_ptr<Obj>> lookup_; void deleteObj(Obj *pObj) { lookup_.erase(pObj->name_); delete pObj; } public: shared_ptr<Obj> get(const string &name) { auto it = lookup_.find(name); if (it != lookup_.end()) { shared_ptr<Obj> obj((it->second).lock()); return obj; } shared_ptr<Obj> instance(new Obj(name), bind(&ObjFactory::deleteObj, shared_from_this(), placeholders::_1)); weak_ptr<Obj> obj(instance); lookup_[name] = obj; return instance; } }; { shared_ptr<ObjFactory> pFactory(new ObjFactory); shared_ptr<Obj> obj1 = pFactory->get("Obj1"); }
Misc 使用shared_ptr會延長ObjectFactory的LifeCycle,如果pFactory已經不在的話,跟本連清理的動作都不用作。 所以程式可以寫成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class ObjFactory : public enable_shared_from_this<ObjFactory> { map<string, weak_ptr<Obj>> lookup_; static void deleteObj(const weak_ptr<ObjFactory> &pWeakFactory, Obj *pObj) { shared_ptr<ObjFactory> pFactory(pWeakFactory.lock()); if (pFactory) { pFactory->lookup_.erase(pObj->name_); } delete pObj; } public: shared_ptr<Obj> get(const string &name) { auto it = lookup_.find(name); if (it != lookup_.end()) { shared_ptr<Obj> obj((it->second).lock()); return obj; } shared_ptr<Obj> instance(new Obj(name), bind(&ObjFactory::deleteObj, weak_ptr<ObjFactory>(shared_from_this()), placeholders::_1)); weak_ptr<Obj> obj(instance); lookup_[name] = obj; return instance; } };
結論 有本好書真的很重要啊,以前我只會用最粗淺的shared_ptr,對於wear_ptr跟其他特性玩全部熟。有了範例之後至少有個基本認識。 至於程式寫得這麼複雜到令人髮指也是不太好,Garbage collection很大部份可以紓解Programmer的負擔。