C++17實(shí)現(xiàn)flyweight_factory模板類及使用示例詳解
FlyWeight Pattern
回顧享元模式,考慮實(shí)作它的各種問(wèn)題。
理論
享元模式,是將復(fù)雜對(duì)象的相同的組成元素抽出并單獨(dú)維護(hù)的一種結(jié)構(gòu)型設(shè)計(jì)模式。這些相同的組成元素被稱為共享元件,它們?cè)谝粋€(gè)單獨(dú)的容器中被唯一性地管理,而復(fù)雜對(duì)象只需持有到該唯一實(shí)例的參考,而無(wú)需重復(fù)創(chuàng)建這樣的相同的元素,從而能夠大幅度地削減內(nèi)存占用。
以字處理器為例,每個(gè)字符都具有獨(dú)立的、區(qū)別于其它字符的特殊屬性:例如字體樣式,背景、邊框、對(duì)齊等等。如果一個(gè)文檔中全部字符都單獨(dú)存儲(chǔ)一份它的所有屬性的副本,那么這將會(huì)是龐大的內(nèi)存需求。但考慮到一大堆(例如1000個(gè))字符可能都有相同的“宋體,9pt”這樣的屬性,那么實(shí)際上我們只需要單獨(dú)存儲(chǔ)一份“宋體,9pt”的字體樣式屬性,而一個(gè)字符只需要一個(gè)指向該字體樣式屬性的指針就可以了,這就比1000個(gè)字符的1000個(gè)字體樣式屬性拷貝要節(jié)約的多。
類似的案例還有相當(dāng)多,例如例子系統(tǒng)中的每個(gè)粒子(例如子彈、彈片,或者敵方飛機(jī))都有一些相同的屬性(例如顏色,輪廓等等)占地不小,但值卻相同。
工廠模式
很容易想到,我們可以在一個(gè)工廠中就地管理享元對(duì)象。當(dāng)客戶以具體值來(lái)請(qǐng)求一個(gè)享元對(duì)象時(shí),工廠會(huì)從一個(gè)字典中檢索享元是否存在,然后返回該元素的參考引用給客戶。如果享元尚未存在,那么工廠會(huì)創(chuàng)建它,然后在返回引用。
不可變性
按照傳統(tǒng)的說(shuō)法,享元模式要求這些相同的部分(享元,相同的組成元素)是不可變的。但這并不是鐵律。
一個(gè)方法是,以一個(gè)享元為整體,我們可以整體修改對(duì)象持有的享元參考。
例如我們正在修改字處理器中的一個(gè)單詞的字體樣式,從“宋體,9pt”改為“黑體,12pt”,那么我們可以直接修改引用指向。也就是說(shuō),我們提供 character.apply_font_style(font_style& style)
這樣的整體修改接口。
另一個(gè)方法可以從更細(xì)的粒度出發(fā)進(jìn)行修改,例如從“宋體,9pt”改為“宋體,10pt”,但在發(fā)生變更時(shí),嘗試從工廠中查證新值的參考。也就是說(shuō),我們提供 character.set_font_size(float pt)
這樣的接口,但在其實(shí)現(xiàn)過(guò)程中記得去查證享元工廠(管理器)以求更新內(nèi)部引用。
C++ 實(shí)現(xiàn)
傳統(tǒng)的享元模式的實(shí)現(xiàn)方式有這樣的示例代碼:
namespace hicc::dp::flyweight::basic { /** * flyweight Design Pattern * * Intent: Lets you fit more objects into the available amount of RAM by sharing * common parts of state between multiple objects, instead of keeping all of the * data in each object. */ struct shared_state { std::string brand_; std::string model_; std::string color_; shared_state(const std::string &brand, const std::string &model, const std::string &color) : brand_(brand) , model_(model) , color_(color) { } friend std::ostream &operator<<(std::ostream &os, const shared_state &ss) { return os << "[ " << ss.brand_ << " , " << ss.model_ << " , " << ss.color_ << " ]"; } }; struct unique_state { std::string owner_; std::string plates_; unique_state(const std::string &owner, const std::string &plates) : owner_(owner) , plates_(plates) { } friend std::ostream &operator<<(std::ostream &os, const unique_state &us) { return os << "[ " << us.owner_ << " , " << us.plates_ << " ]"; } }; /** * The flyweight stores a common portion of the state (also called intrinsic * state) that belongs to multiple real business entities. The flyweight accepts * the rest of the state (extrinsic state, unique for each entity) via its * method parameters. */ class flyweight { private: shared_state *shared_state_; public: flyweight(const shared_state *o) : shared_state_(new struct shared_state(*o)) { } flyweight(const flyweight &o) : shared_state_(new struct shared_state(*o.shared_state_)) { } ~flyweight() { delete shared_state_; } shared_state *state() const { return shared_state_; } void Operation(const unique_state &unique_state) const { std::cout << "flyweight: Displaying shared (" << *shared_state_ << ") and unique (" << unique_state << ") state.\n"; } }; /** * The flyweight Factory creates and manages the flyweight objects. It ensures * that flyweights are shared correctly. When the client requests a flyweight, * the factory either returns an existing instance or creates a new one, if it * doesn't exist yet. */ class flyweight_factory { std::unordered_map<std::string, flyweight> flyweights_; std::string key(const shared_state &ss) const { return ss.brand_ + "_" + ss.model_ + "_" + ss.color_; } public: flyweight_factory(std::initializer_list<shared_state> lists) { for (const shared_state &ss : lists) { this->flyweights_.insert(std::make_pair<std::string, flyweight>(this->key(ss), flyweight(&ss))); } } /** * Returns an existing flyweight with a given state or creates a new one. */ flyweight get(const shared_state &shared_state) { std::string key = this->key(shared_state); if (this->flyweights_.find(key) == this->flyweights_.end()) { std::cout << "flyweight_factory: Can't find a flyweight, creating new one.\n"; this->flyweights_.insert(std::make_pair(key, flyweight(&shared_state))); } else { std::cout << "flyweight_factory: Reusing existing flyweight.\n"; } return this->flyweights_.at(key); } void list() const { size_t count = this->flyweights_.size(); std::cout << "\nflyweight_factory: I have " << count << " flyweights:\n"; for (std::pair<std::string, flyweight> pair : this->flyweights_) { std::cout << pair.first << "\n"; } } }; // ... void AddCarToPoliceDatabase( flyweight_factory &ff, const std::string &plates, const std::string &owner, const std::string &brand, const std::string &model, const std::string &color) { std::cout << "\nClient: Adding a car to database.\n"; const flyweight &flyweight = ff.get({brand, model, color}); // The client code either stores or calculates extrinsic state and passes it // to the flyweight's methods. flyweight.Operation({owner, plates}); } } // namespace hicc::dp::flyweight::basic void test_flyweight_basic() { using namespace hicc::dp::flyweight::basic; flyweight_factory *factory = new flyweight_factory({ {"Chevrolet", "Camaro2018", "pink"}, {"Mercedes Benz", "C300", "black"}, {"Mercedes Benz", "C500", "red"}, {"BMW", "M5", "red"}, {"BMW", "X6", "white"} }); factory->list(); AddCarToPoliceDatabase(*factory, "CL234IR", "James Doe", "BMW", "M5", "red"); AddCarToPoliceDatabase(*factory, "CL234IR", "James Doe", "BMW", "X1", "red"); factory->list(); delete factory; }
其輸出結(jié)果如同這樣:
--- BEGIN OF test_flyweight_basic ----------------------
flyweight_factory: I have 5 flyweights:
BMW_X6_white
Mercedes Benz_C500_red
Mercedes Benz_C300_black
BMW_M5_red
Chevrolet_Camaro2018_pinkClient: Adding a car to database.
flyweight_factory: Reusing existing flyweight.
flyweight: Displaying shared ([ BMW , M5 , red ]) and unique ([ James Doe , CL234IR ]) state.Client: Adding a car to database.
flyweight_factory: Can't find a flyweight, creating new one.
flyweight: Displaying shared ([ BMW , X1 , red ]) and unique ([ James Doe , CL234IR ]) state.flyweight_factory: I have 6 flyweights:
BMW_X1_red
Mercedes Benz_C300_black
BMW_X6_white
Mercedes Benz_C500_red
BMW_M5_red
Chevrolet_Camaro2018_pink
--- END OF test_flyweight_basic ----------------------
可以看到,像 [ BMW , X1 , red ]
這樣的一個(gè)享元,單個(gè)實(shí)例較大(數(shù)十、數(shù)百乃至數(shù)十K 字節(jié)),而引用參考不過(guò)是一個(gè)指針的大?。ㄍǔJ?64 bytes on 64-bit OS),那么最終節(jié)省的內(nèi)存是非??捎^的。
元編程中的 FlyWeight Pattern
上面的示例,已經(jīng)是舊風(fēng)格了,C++11 以后我們需要大量地使用智能指針、以及模板語(yǔ)法,而在 C++17 之后更好的原位構(gòu)造能力允許我們的代碼能夠更加 meaningful。
flyweight_factory
一個(gè)想法是,我們認(rèn)為一個(gè)盡可能通用的享元工廠可能是有利于代碼書寫的。所以我們嘗試這樣一個(gè)享元工廠模板:
namespace hicc::dp::flyweight::meta { template<typename shared_t = shared_state_impl, typename unique_t = unique_state_impl> class flyweight { std::shared_ptr<shared_t> shared_state_; public: flyweight(flyweight const &o) : shared_state_(std::move(o.shared_state_)) { } flyweight(shared_t const &o) : shared_state_(std::make_shared<shared_t>(o)) { } ~flyweight() {} auto state() const { return shared_state_; } auto &state() { return shared_state_; } void Operation(const unique_t &unique_state) const { std::cout << "flyweight: Displaying shared (" << *shared_state_ << ") and unique (" << unique_state << ") state.\n"; } friend std::ostream &operator<<(std::ostream &os, const flyweight &o) { return os << *o.shared_state_; } }; template<typename shared_t = shared_state_impl, typename unique_t = unique_state_impl, typename flyweight_t = flyweight<shared_t, unique_t>, typename hasher_t = std::hash<shared_t>> class flyweight_factory { public: flyweight_factory() {} explicit flyweight_factory(std::initializer_list<shared_t> args) { for (auto const &ss : args) { flyweights_.emplace(_hasher(ss), flyweight_t(ss)); } } flyweight_t get(shared_t const &shared_state) { auto key = _hasher(shared_state); if (this->flyweights_.find(key) == this->flyweights_.end()) { std::cout << "flyweight_factory: Can't find a flyweight, creating new one.\n"; this->flyweights_.emplace(key, flyweight_t(shared_state)); } else { std::cout << "flyweight_factory: Reusing existing flyweight.\n"; } return this->flyweights_.at(key); } void list() const { size_t count = this->flyweights_.size(); std::cout << "\nflyweight_factory: I have " << count << " flyweights:\n"; for (auto const &pair : this->flyweights_) { std::cout << pair.first << " => " << pair.second << "\n"; } } private: std::unordered_map<std::size_t, flyweight_t> flyweights_; hasher_t _hasher{}; }; } // namespace hicc::dp::flyweight::meta
然后我們就可以以派生類的方式直接使用這個(gè)享元工廠了:
class vehicle : public flyweight_factory<shared_state_impl, unique_state_impl> { public: using flyweight_factory<shared_state_impl, unique_state_impl>::flyweight_factory; void AddCarToPoliceDatabase( const std::string &plates, const std::string &owner, const std::string &brand, const std::string &model, const std::string &color) { std::cout << "\nClient: Adding a car to database.\n"; auto const &flyweight = this->get({brand, model, color}); flyweight.Operation({owner, plates}); } };
其中 using flyweight_factory<shared_state_impl, unique_state_impl>::flyweight_factory;
是 C++17 以后的新語(yǔ)法,它將父類的所有構(gòu)造函數(shù)原樣復(fù)制給派生類,從而讓你不必拷貝粘貼代碼然后修改類名。
在 vehicle
模板類中我們使用默認(rèn)的 flyweight<shared_t, unique_t>
,但你可以在 flyweight_factory
的模板參數(shù)中修改它以便提供你自己的享元類具體實(shí)現(xiàn)。
測(cè)試代碼
void test_flyweight_meta() { using namespace hicc::dp::flyweight::meta; auto factory = std::make_unique<vehicle>( std::initializer_list<shared_state_impl>{ {"Chevrolet", "Camaro2018", "pink"}, {"Mercedes Benz", "C300", "black"}, {"Mercedes Benz", "C500", "red"}, {"BMW", "M5", "red"}, {"BMW", "X6", "white"}}); factory->list(); factory->AddCarToPoliceDatabase("CL234IR", "James Doe", "BMW", "M5", "red"); factory->AddCarToPoliceDatabase("CL234IR", "James Doe", "BMW", "X1", "red"); factory->list(); }
附加
我們使用了稍稍不同的基礎(chǔ)類 shared_state_impl
以及 unique_state_impl
:
namespace hicc::dp::flyweight::meta { struct shared_state_impl { std::string brand_; std::string model_; std::string color_; shared_state_impl(const std::string &brand, const std::string &model, const std::string &color) : brand_(brand) , model_(model) , color_(color) { } shared_state_impl(shared_state_impl const &o) : brand_(o.brand_) , model_(o.model_) , color_(o.color_) { } friend std::ostream &operator<<(std::ostream &os, const shared_state_impl &ss) { return os << "[ " << ss.brand_ << " , " << ss.model_ << " , " << ss.color_ << " ]"; } }; struct unique_state_impl { std::string owner_; std::string plates_; unique_state_impl(const std::string &owner, const std::string &plates) : owner_(owner) , plates_(plates) { } friend std::ostream &operator<<(std::ostream &os, const unique_state_impl &us) { return os << "[ " << us.owner_ << " , " << us.plates_ << " ]"; } }; } // namespace hicc::dp::flyweight::meta namespace std { template<> struct hash<hicc::dp::flyweight::meta::shared_state_impl> { typedef hicc::dp::flyweight::meta::shared_state_impl argument_type; typedef std::size_t result_type; result_type operator()(argument_type const &s) const { result_type h1(std::hash<std::string>{}(s.brand_)); hash_combine(h1, s.model_, s.color_); return h1; } }; } // namespace std
這是因?yàn)槲覀冊(cè)?flyweight_factory 中使用了 std::hash 技術(shù)來(lái)管理一個(gè)享元的鍵值,所以我們必須明確地實(shí)現(xiàn) shared_state_impl 的 std::hash 特化版本。
而在這個(gè)特化版本中我們又利用了一個(gè)特別的 hash_combine 函數(shù)。
hash_combine
這是一個(gè)技術(shù)性很強(qiáng)的概念,因?yàn)樗婕暗搅艘粋€(gè)神奇的幻數(shù)(magicnum)0x9e3779b9。
我們?cè)?nbsp;hicc-cxx/cmdr-cxx 中提供了一個(gè)源于 boost::hash_combine 的同名擴(kuò)展:
namespace std { template<typename T, typename... Rest> inline void hash_combine(std::size_t &seed, T const &t, Rest &&...rest) { std::hash<T> hasher; seed ^= 0x9e3779b9 + (seed << 6) + (seed >> 2) + hasher(t); int i[] = {0, (hash_combine(seed, std::forward<Rest>(rest)), 0)...}; (void) (i); } template<typename T> inline void hash_combine(std::size_t &seed, T const &v) { std::hash<T> hasher; seed ^= 0x9e3779b9 + (seed << 6) + (seed >> 2) + hasher(v); } } // namespace std
它的作用在于計(jì)算一系列的對(duì)象的 hash 值并組合它們。
關(guān)于如何正確地組合一堆 hash 值,較為簡(jiǎn)單地方法是:
std::size_t h1 = std::hash<std::string>("hello"); std::size_t h2 = std::hash<std::string>("world"); std::size_t h = h1 | (h2 << 1);
但仍然有更多的探討,其中得到了公認(rèn)的最佳手段(C++中)是源自于 boost::hash_combine 實(shí)現(xiàn)代碼的一個(gè)方法:
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
就目前已知的學(xué)術(shù)研究中,這是最佳的。
那么誰(shuí)制造了這么奇幻的一個(gè)神經(jīng)質(zhì)數(shù)(golden ratio)呢,大體可考的原作應(yīng)該是: A Hash Function for Hash Table Lookup 或 Hash Functions for Hash Table Lookup 。原作者 Bob Jenkins,原發(fā)于 DDJ 刊物 1997 年,代碼大約是成形于 1996 年。而這個(gè)數(shù)嘛,源于這個(gè)表達(dá)式:
$\frac{2^{32}}{\frac{1+\sqrt{5}}{2}}$ (
Epilogue
好,雖然不算太盡人意,但我確實(shí)實(shí)現(xiàn)了一個(gè) C++17 的勉強(qiáng)比較通用的 flyweight_factory 模板類,就將就了吧。
以上就是C++17實(shí)現(xiàn)flyweight_factory模板類及使用示例詳解的詳細(xì)內(nèi)容,更多關(guān)于C++17 flyweight_factory模板類的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Linux C 時(shí)間函數(shù)應(yīng)用
本文是關(guān)于Linux C時(shí)間函數(shù) time_t struct tm 進(jìn)行了詳細(xì)的分析介紹并有應(yīng)用實(shí)例,希望能幫到有需要的同學(xué)2016-07-07C語(yǔ)言用封裝方法實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言用封裝方法實(shí)現(xiàn)飛機(jī)大戰(zhàn)游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05