C++通用動(dòng)態(tài)抽象工廠的實(shí)現(xiàn)詳解
背景
一開始,我是想到了下面這個(gè)場景:
struct A { void Foo() {} }; struct B { void Bar() { A().Foo(); } };
如上面代碼,B的Bar中構(gòu)造了A,然后調(diào)用了A的Foo函數(shù)。假如我想在別的場景中復(fù)用B的Bar,但是這時(shí)A的Foo就不符合需求了,需要定制,于是我們想到可以用多態(tài),把A的Foo改成虛函數(shù),然后寫出A1:
struct A { A() = default; virtual ~A() = default; virtual void Foo() {} }; struct A1 : public A { void Foo() override {} };
B不應(yīng)該知道A1的存在,為了讓B用上A1,同時(shí)也為以后可能會(huì)拓展的A2、A3做準(zhǔn)備,我們寫了個(gè)A的工廠函數(shù)GetA()來生成A。于是代碼變成:
std::unique_ptr<A> GetA() { if (Condition1()) { return std::unique_ptr<A>(new A1()); } else { return std::unique_ptr<A>(new A()); } } struct B { void Bar() { GetA()->Foo(); } };
如果B中還要構(gòu)造別的C、D等類,難道我們要為每個(gè)類都寫一個(gè)工廠函數(shù)嗎?這成本也太高了,而且可能大部分類都只有一個(gè),不用搞繼承,寫工廠函數(shù)就是無用功。那么,有沒有一種通用的方式可以在寫B(tài)代碼的時(shí)候就對(duì)A、C、D等類的構(gòu)造都留一手,使得以后可以由B外的代碼控制實(shí)際構(gòu)造的是A1等,而不需要修改B的代碼?這就是我寫動(dòng)態(tài)抽象工廠的原始需求。
實(shí)現(xiàn)
思路很簡單,就是為每個(gè)類都自動(dòng)生成一個(gè)工廠函數(shù)就好了,然后在B中不直接構(gòu)造A、C、D等對(duì)象,而是都調(diào)用對(duì)應(yīng)類的工廠函數(shù)。然后這個(gè)自動(dòng)生成的工廠默認(rèn)就是構(gòu)造原始的類的對(duì)象,比如A,也有接口可以改成生成子類,比如A1。對(duì)每個(gè)類生成一個(gè)工廠函數(shù)自然就想到用模板了。至于這個(gè)接口怎么實(shí)現(xiàn),就有兩大分支,分別是編譯期和運(yùn)行期。編譯期一般就是用模板特化了。我覺得運(yùn)行期會(huì)更有趣,用法會(huì)更多,就選了運(yùn)行期。
運(yùn)行期的意思就是要搞個(gè)變量來存下這個(gè)修改的工廠函數(shù),自然就想到用std::function。當(dāng)然免不了的是要把這個(gè)變量傳給B。如果管理A的是一個(gè)變量,管理C、D的是另外兩個(gè)變量,那就要傳很多很多變量給B,這樣也太繁瑣了,所以應(yīng)該一個(gè)變量存下所有類的工廠函數(shù),然后把這個(gè)變量傳遍所有需要使用工廠函數(shù)的對(duì)象或函數(shù)當(dāng)中。所以這個(gè)變量的類型是一個(gè)確定的類型,不能帶模板(或者說模板參數(shù)不能跟工廠對(duì)應(yīng)的類相關(guān),如A、C、D等)。那么模板就放到方法當(dāng)中了。很自然地,這個(gè)類型的接口就應(yīng)該是這樣:
struct DynamicAbstractFactory { template <typename T, typename... Args> std::unique_ptr<T> New(Args&&...); };
這里插一段,為什么叫動(dòng)態(tài)抽象工廠呢?按照我的理解,工廠模式就是實(shí)現(xiàn)一個(gè)返回T*的函數(shù)F,里面用ifelse來控制最終返回的是T還是T的某個(gè)子類。抽象工廠模式就是連這個(gè)函數(shù)F都是可變的。動(dòng)態(tài)是指這個(gè)F是運(yùn)行時(shí)可變。
那么這個(gè)接口怎么實(shí)現(xiàn)呢?我的想法是用一個(gè)map來記錄類型組合(T, Args&&...)到工廠函數(shù)std::function<T*(Args&&...)>的映射,并存儲(chǔ)std::function<T*(Args&&...)>。New的實(shí)現(xiàn)就是查找map中有沒有對(duì)應(yīng)的工廠函數(shù),有就調(diào)用工廠函數(shù),沒有就調(diào)用T本身的構(gòu)造函數(shù)。當(dāng)然,也需要提供一個(gè)接口來修改這個(gè)map。
要實(shí)現(xiàn)這個(gè)map還有三個(gè)細(xì)節(jié):
- 存儲(chǔ)的std::function<T*(Args&&...)>是不同的類型,需要用類型擦除存儲(chǔ)。如果可用C++17的話可直接用std::any,但我的環(huán)境有些老代碼用gcc7編不過,所以還是只能用C++11,于是用std::shared_ptr<void>來代替(我一開始還是用std::unique_ptr+虛析構(gòu)函數(shù)+繼承來實(shí)現(xiàn)的,后來才知道std::shared_ptr自帶這個(gè)功能)。
- map的key是一個(gè)類型組合,就用std::type_index吧。由于std::function<T*(Args&&...)>已經(jīng)把整個(gè)類型組合包進(jìn)去了,而且一定會(huì)實(shí)例化,就直接用它吧。于是key就成了std::type_index(typeid(std::function<T*(Args&&...)>))。
- 由于接口New(Args&&...)的每個(gè)參數(shù)類型Args&&都是引用類型,為了保持一致性,為了map能找到正確的函數(shù),要求std::function中的每個(gè)參數(shù)也是引用類型,所以上面都寫作std::function<T*(Args&&...)>。比如std::function<T*(int)>會(huì)轉(zhuǎn)換成std::function<T*(int&&)>。
再加上修改map的接口SetFunc,第一版的動(dòng)態(tài)抽象工廠就做好了:
class DynamicAbstractFactory { public: template <typename T, typename... Args> std::unique_ptr<T> New(Args&&... args) { auto iter = index2func_.find(std::type_index(typeid(std::function<T*(Args&&...)>))); if (iter != index2func_.end()) { return std::unique_ptr<T>((*reinterpret_cast<std::function<T*(Args&&...)>*>(iter->second.get()))(std::forward<Args>(args)...)); } return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); } template <typename T, typename... Args> void SetFunc(std::function<T*(Args...)>&& func) { index2func_[std::type_index(typeid(std::function<T*(Args&&...)>))] = std::make_shared<std::function<T*(Args&&...)>>(std::move(func)); } protected: std::unordered_map<std::type_index, std::shared_ptr<void>> index2func_; };
于是B的代碼及使用就變成這樣:
class B { public: B(DynamicAbstractFactory& factory) : factory_(factory) {} void Bar() { factory_.New<A>()->Foo(); factory_.New<C>(); factory_.New<D>(); } protected: DynamicAbstractFactory& factory_; }; // 舊環(huán)境,用原始A、C、D // 當(dāng)然B也可以用factory來生成 void Run() { DynamicAbstractFactory factory; factory.New<B>(factory)->Bar(); } // 新環(huán)境,用A1、C、D void Run1() { DynamicAbstractFactory factory; std::function<A*()> get_a = []() { return new A1(); }; factory.SetFunc<A>(std::move(get_a)); factory.New<B>(factory)->Bar(); }
這樣就滿足了一開始的需求,B在構(gòu)造A、C、D的時(shí)候都留了一手,B并不需要知道實(shí)際構(gòu)造的是什么,在B的外部,Run()和Run1(),可控制在B里具體要構(gòu)造的對(duì)象。
寫完后發(fā)現(xiàn)這東西作用不止于此,下面寫寫一些擴(kuò)展用法。
寄存參數(shù)
子類的構(gòu)造函數(shù)的參數(shù)可以跟父類不一樣,通過lambda捕獲來寄存。
struct A2 : public A { A2(int i) {} void Foo() override {} }; void Run2() { DynamicAbstractFactory factory; int i = 0; std::function<A*()> get_a = [i]() { return new A2(i); }; factory.SetFunc<A>(std::move(get_a)); factory.New<B>(factory)->Bar(); }
存儲(chǔ)所有構(gòu)造出來的對(duì)象
上面的接口返回std::unique_ptr,還要管理對(duì)象生命周期,不如更進(jìn)一步,用factory來管理所有它構(gòu)造的對(duì)象,在factory析構(gòu)時(shí)統(tǒng)一析構(gòu)。因?yàn)槲乙话銓懞笈_(tái)rpc接口,可以在rpc請(qǐng)求開始時(shí)構(gòu)造factory,在構(gòu)造好回包后析構(gòu)factory,這樣在整個(gè)請(qǐng)求周期構(gòu)造的對(duì)象都在,指針不會(huì)失效,而且在請(qǐng)求結(jié)束后可以很方便地進(jìn)行異步析構(gòu),直接把factory丟到析構(gòu)線程就好。
于是New接口返回值由std::unique_ptr改成T*,同時(shí)New可能會(huì)造成誤解,改成Get。當(dāng)然,存儲(chǔ)肯定要用到類型擦除存儲(chǔ)。就成了下面這樣:
class GeneralStorage { public: GeneralStorage(size_t reserve_size = 256) { storage_.reserve(reserve_size); } ~GeneralStorage() { // 保證按添加順序逆序析構(gòu) while (storage_.size() > 0) { storage_.pop_back(); } } template <class T, class... Args> T* EmplaceBack(Args&&... args) { auto p_obj = std::make_shared<T>(std::forward<Args>(args)...); storage_.push_back(p_obj); return p_obj.get(); } protected: std::vector<std::shared_ptr<void>> storage_; }; class DynamicAbstractFactoryWithStorage { public: template <typename T, typename... Args> T* Get(Args&&... args) { auto iter = index2func_.find(std::type_index(typeid(std::function<T*(Args&&...)>))); if (iter != index2func_.end()) { return (*reinterpret_cast<std::function<T*(Args&&...)>*>(iter->second.get()))(std::forward<Args>(args)...); } return storage_.EmplaceBack<T>(std::forward<Args>(args)...)); } template <typename T, typename... Args> void SetFunc(std::function<T*(Args...)>&& func) { index2func_[std::type_index(typeid(std::function<T*(Args&&...)>))] = std::make_shared<std::function<T*(Args&&...)>>(std::move(func)); } protected: std::unordered_map<std::type_index, std::shared_ptr<void>> index2func_; GeneralStorage storage_; };
有個(gè)細(xì)節(jié)是對(duì)于改變過的工廠函數(shù)返回的指針是不應(yīng)該存在storage_中的,而應(yīng)該是在工廠函數(shù)中把對(duì)象存進(jìn)storage_。上面的Run1()應(yīng)該改成這樣:
void Run1() { DynamicAbstractFactoryWithStorage factory; std::function<A*()> get_a = [&factory]() { return factory.Get<A1>(); }; factory.SetFunc<A>(std::move(get_a)); factory.Get<B>(factory)->Bar(); }
寄存指針,可析構(gòu)的單例
當(dāng)返回值由std::unique_ptr改成T*,就可以實(shí)現(xiàn)寄存指針了。可析構(gòu)的單例指每次請(qǐng)求都重新構(gòu)造,在請(qǐng)求結(jié)束后析構(gòu),但是請(qǐng)求之中只構(gòu)造一次??聪旅胬樱?/p>
struct C { C(DynamicAbstractFactoryWithStorage& factory) { std::function<C*(DynamicAbstractFactoryWithStorage&)> func = [this](DynamicAbstractFactoryWithStorage& factory) { return this; }; factory.SetFunc<C>(std::move(func)); } }; void Run() { DynamicAbstractFactoryWithStorage factory; factory.Get<C>(factory); // 構(gòu)造C,并通過SetFunc把對(duì)象的指針寄存到factory中 factory.Get<C>(factory); // 調(diào)用C構(gòu)造函數(shù)中的func,直接返回寄存的指針,不重復(fù)構(gòu)造C // factory析構(gòu)時(shí)C的對(duì)象將被析構(gòu) }
裝飾工廠函數(shù),責(zé)任鏈工廠
只要再加個(gè)接口GetFunc來獲取當(dāng)前的工廠函數(shù),就可以對(duì)工廠函數(shù)玩裝飾模式了。
GetFunc接口:
// 其它代碼與上面一樣 class DynamicAbstractFactoryWithStorage { public: template <typename T, typename... Args> std::function<T*(Args&&...)> GetFunc() { auto iter = index2func_.find(std::type_index(typeid(std::function<T*(Args&&...)>))); if (iter != index2func_.end()) { return *reinterpret_cast<std::function<T*(Args&&...)>*>(iter->second.get()); } std::function<T*(Args&&...)> default_func = [this](Args&&... args) { return storage_.EmplaceBack<T>(std::forward<Args>(args)...)); }; return default_func; } };
統(tǒng)計(jì)調(diào)用次數(shù):
struct C { C(DynamicAbstractFactoryWithStorage& factory) { std::function<C*(DynamicAbstractFactoryWithStorage&)> func = [this](DynamicAbstractFactoryWithStorage& factory) { return this; }; factory.SetFunc<C>(std::move(func)); } uint32_t cnt_ = 0; }; void Run() { DynamicAbstractFactoryWithStorage factory; auto func = factory.GetFunc<A>(); std::function<A*()> get_a = [func, &factory]() { ++factory.Get<C>()->cnt_; return func(); }; factory.SetFunc<A>(std::move(get_a)); factory.Get<B>(factory)->Bar(); }
用責(zé)任鏈模式搞個(gè)工廠:
struct D { D() {} D(int i) {} }; struct D1 : public D { D1(int i) {} static void SetFactoryD(DynamicAbstractFactoryWithStorage& factory) { // GetFunc的結(jié)果是std::fuction<D*(int&&)>類型的,這里經(jīng)過了一次類型轉(zhuǎn)換 std::function<D*(int)> func = factory.GetFunc<D, int>(); std::function<D*(int)> new_func = [func, &factory](int i) -> D* { // 責(zé)任鏈模式 if (Check(i)) { return factory.Get<D1>(i); } else { return func(i); } }; factory.SetFunc<D>(std::move(new_func)); } // 構(gòu)造D1的條件 static bool Check(int i) { return i == 1; } }; // 與D1類似,除了Check struct D2 : public D { D2(int i) {} static void SetFactoryD(DynamicAbstractFactoryWithStorage& factory) { std::function<D*(int)> func = factory.GetFunc<D, int>(); std::function<D*(int)> new_func = [func, &factory](int i) -> D* { if (Check(i)) { return factory.Get<D2>(i); } else { return func(i); } }; factory.SetFunc<D>(std::move(new_func)); } // 構(gòu)造D2的條件 static bool Check(int i) { return i == 2; } }; void Run() { DynamicAbstractFactoryWithStorage factory; D1::SetFactoryD(factory); D2::SetFactoryD(factory); factory.Get<D>(0); // D factory.Get<D>(1); // D1 factory.Get<D>(2); // D2 }
允許構(gòu)造函數(shù)之外的參數(shù)組合
上面的實(shí)現(xiàn)要求new T(std::forward(args)...)能合法生成一個(gè)T對(duì)象指針,在一些情況下很難做到,比如T中有難以初始化的成員,又比如T是一個(gè)抽象類:
struct E { E() = default; virtual ~E() = default; virtual void Foo() = 0; };
這樣就要修改Get接口的邏輯,改成如果能合法調(diào)用構(gòu)造函數(shù),就調(diào)用,否則就不調(diào)用。但是這樣放開之后,就各種參數(shù)組合都可以搞了,我覺得這樣可能會(huì)很混亂,這邊設(shè)置了這個(gè)參數(shù)組合,那邊設(shè)置了另外的參數(shù)組合,不知道一共設(shè)置了哪幾種參數(shù)組合。我覺得還是要加點(diǎn)限制,就規(guī)定參數(shù)組合必須在基類中定義。規(guī)定了一個(gè)方法名FactoryGet,所有非構(gòu)造函數(shù)的參數(shù)組合要定義一個(gè)靜態(tài)FactoryGet方法,方法返回T*,比如:
struct E { E() = default; static E* FactoryGet(int) { return nullptr; } virtual ~E() = default; virtual void Foo() = 0; };
這樣Get接口的邏輯就可以改成如果能合法調(diào)用構(gòu)造函數(shù),就調(diào)用,否則就調(diào)用對(duì)應(yīng)的FactoryGet方法,其他參數(shù)組合將會(huì)編譯報(bào)錯(cuò)。同時(shí)也規(guī)定FactoryGet獲得的指針不存進(jìn)通用存儲(chǔ)。于是DynamicAbstractFactoryWithStorage就改成這樣:
// new T(std::forward<Args>(args)...) // T::FactoryGet(std::forward<Args>(args)...) // 要求上面兩個(gè)表達(dá)式有且僅有一個(gè)合法并且返回T*,Get調(diào)用合法的那個(gè)。 template <typename T, typename F, typename = void> struct DefaultGet; template <typename T, typename... Args> struct DefaultGet<T, void(Args...), typename std::enable_if<std::is_same<decltype(std::decay<T>::type::FactoryGet(std::forward<Args>(*reinterpret_cast<typename std::decay<Args>::type*>(0))...)), T*>::value, void>::type> { static T* Get(GeneralStorage& storage, Args&&... args) { return T::FactoryGet(std::forward<Args>(args)...); } }; template <typename T, typename... Args> struct DefaultGet<T, void(Args...), typename std::enable_if<std::is_same<decltype(new typename std::decay<T>::type(std::forward<Args>(*reinterpret_cast<typename std::decay<Args>::type*>(0))...)), typename std::decay<T>::type*>::value, void>::type> { static T* Get(GeneralStorage& storage, Args&&... args) { return storage.EmplaceBack<typename std::decay<T>::type>(std::forward<Args>(args)...); } }; class DynamicAbstractFactoryWithStorage { public: // 每個(gè)Args都要是引用 template <typename T, typename... Args> using FuncType = std::function<T*(Args&&...)>; template <typename T, typename... Args> T* Get(Args&&... args) { auto iter = index2func_.find(std::type_index(typeid(FuncType<T, Args...>))); if (iter != index2func_.end()) { return (*reinterpret_cast<FuncType<T, Args...>*>(iter->second.get()))(std::forward<Args>(args)...); } return DefaultGet<T, void(Args&&...)>::Get(storage_, std::forward<Args>(args)...); } template <typename T, typename... Args> void SetFunc(std::function<T*(Args...)>&& func) { index2func_[std::type_index(typeid(FuncType<T, Args...>))] = std::make_shared<FuncType<T, Args...>>(std::move(func)); } template <typename T, typename... Args> FuncType<T, Args...> GetFunc() { auto iter = index2func_.find(std::type_index(typeid(FuncType<T, Args...>))); if (iter != index2func_.end()) { return *reinterpret_cast<FuncType<T, Args...>*>(iter->second.get()); } FuncType<T, Args...> default_func = [this](Args&&... args) { return DefaultGet<T, void(Args&&...)>::Get(storage_, std::forward<Args>(args)...); }; return default_func; } protected: std::unordered_map<std::type_index, std::shared_ptr<void>> index2func_; GeneralStorage storage_; };
這樣E就能像上面那樣用了。另外,想要返回const指針也是可以的。
struct E { E() = default; // 返回值改成了const E* static const E* FactoryGet(int) { return nullptr; } virtual ~E() = default; virtual void Foo() = 0; }; struct E1 : public E { E1(int i) {} static void SetFactoryE(DynamicAbstractFactoryWithStorage& factory) { std::function<const E*(int)> func = factory.GetFunc<const E, int>(); std::function<const E*(int)> new_func = [func, &factory](int i) -> const E* { if (Check(i)) { return factory.Get<const E1>(i); } else { return func(i); } }; factory.SetFunc<const E>(std::move(new_func)); } static bool Check(int i) { return i == 1; } void Foo() override {} }; void Run() { DynamicAbstractFactoryWithStorage factory; E1::SetFactoryE(factory); factory.Get<const E>(0); // nullptr factory.Get<const E>(1); // const E1* }
總結(jié)
到此這篇關(guān)于C++通用動(dòng)態(tài)抽象工廠的文章就介紹到這了,更多相關(guān)C++通用動(dòng)態(tài)抽象工廠內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺談C語言中的sizeof()和strlen()的區(qū)別
本文主要介紹了C語言中的sizeof()和strlen()的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05C++11/14 線程的創(chuàng)建與分離的實(shí)現(xiàn)
這篇文章主要介紹了C++11/14 線程的創(chuàng)建與分離的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-01-01C語言中g(shù)etchar和putchar的使用方法詳解
我們知道scanf函數(shù)可以從鍵盤輸入信息,而printf則可以輸出信息,同樣地,getchar和putchar也有同樣的功能,下面我來給大家介紹putchar和getchar的使用方法,需要的朋友可以參考下2023-08-08C語言結(jié)構(gòu)體成員賦值的深拷貝與淺拷貝詳解
C語言中的淺拷貝是指在拷貝過程中,對(duì)于指針型成員變量只拷貝指針本身,而不拷貝指針?biāo)赶虻哪繕?biāo),它按字節(jié)復(fù)制的。深拷貝除了拷貝其成員本身的值之外,還拷貝成員指向的動(dòng)態(tài)內(nèi)存區(qū)域內(nèi)容。本文將通過示例和大家詳細(xì)說說C語言的深拷貝與淺拷貝,希望對(duì)你有所幫助2022-09-09