C++通用動態(tài)抽象工廠的實現(xiàn)詳解
背景
一開始,我是想到了下面這個場景:
struct A {
void Foo() {}
};
struct B {
void Bar() {
A().Foo();
}
};如上面代碼,B的Bar中構(gòu)造了A,然后調(diào)用了A的Foo函數(shù)。假如我想在別的場景中復用B的Bar,但是這時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不應該知道A1的存在,為了讓B用上A1,同時也為以后可能會拓展的A2、A3做準備,我們寫了個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等類,難道我們要為每個類都寫一個工廠函數(shù)嗎?這成本也太高了,而且可能大部分類都只有一個,不用搞繼承,寫工廠函數(shù)就是無用功。那么,有沒有一種通用的方式可以在寫B(tài)代碼的時候就對A、C、D等類的構(gòu)造都留一手,使得以后可以由B外的代碼控制實際構(gòu)造的是A1等,而不需要修改B的代碼?這就是我寫動態(tài)抽象工廠的原始需求。
實現(xiàn)
思路很簡單,就是為每個類都自動生成一個工廠函數(shù)就好了,然后在B中不直接構(gòu)造A、C、D等對象,而是都調(diào)用對應類的工廠函數(shù)。然后這個自動生成的工廠默認就是構(gòu)造原始的類的對象,比如A,也有接口可以改成生成子類,比如A1。對每個類生成一個工廠函數(shù)自然就想到用模板了。至于這個接口怎么實現(xiàn),就有兩大分支,分別是編譯期和運行期。編譯期一般就是用模板特化了。我覺得運行期會更有趣,用法會更多,就選了運行期。
運行期的意思就是要搞個變量來存下這個修改的工廠函數(shù),自然就想到用std::function。當然免不了的是要把這個變量傳給B。如果管理A的是一個變量,管理C、D的是另外兩個變量,那就要傳很多很多變量給B,這樣也太繁瑣了,所以應該一個變量存下所有類的工廠函數(shù),然后把這個變量傳遍所有需要使用工廠函數(shù)的對象或函數(shù)當中。所以這個變量的類型是一個確定的類型,不能帶模板(或者說模板參數(shù)不能跟工廠對應的類相關,如A、C、D等)。那么模板就放到方法當中了。很自然地,這個類型的接口就應該是這樣:
struct DynamicAbstractFactory {
template <typename T, typename... Args>
std::unique_ptr<T> New(Args&&...);
};這里插一段,為什么叫動態(tài)抽象工廠呢?按照我的理解,工廠模式就是實現(xiàn)一個返回T*的函數(shù)F,里面用ifelse來控制最終返回的是T還是T的某個子類。抽象工廠模式就是連這個函數(shù)F都是可變的。動態(tài)是指這個F是運行時可變。
那么這個接口怎么實現(xiàn)呢?我的想法是用一個map來記錄類型組合(T, Args&&...)到工廠函數(shù)std::function<T*(Args&&...)>的映射,并存儲std::function<T*(Args&&...)>。New的實現(xiàn)就是查找map中有沒有對應的工廠函數(shù),有就調(diào)用工廠函數(shù),沒有就調(diào)用T本身的構(gòu)造函數(shù)。當然,也需要提供一個接口來修改這個map。
要實現(xiàn)這個map還有三個細節(jié):
- 存儲的std::function<T*(Args&&...)>是不同的類型,需要用類型擦除存儲。如果可用C++17的話可直接用std::any,但我的環(huán)境有些老代碼用gcc7編不過,所以還是只能用C++11,于是用std::shared_ptr<void>來代替(我一開始還是用std::unique_ptr+虛析構(gòu)函數(shù)+繼承來實現(xiàn)的,后來才知道std::shared_ptr自帶這個功能)。
- map的key是一個類型組合,就用std::type_index吧。由于std::function<T*(Args&&...)>已經(jīng)把整個類型組合包進去了,而且一定會實例化,就直接用它吧。于是key就成了std::type_index(typeid(std::function<T*(Args&&...)>))。
- 由于接口New(Args&&...)的每個參數(shù)類型Args&&都是引用類型,為了保持一致性,為了map能找到正確的函數(shù),要求std::function中的每個參數(shù)也是引用類型,所以上面都寫作std::function<T*(Args&&...)>。比如std::function<T*(int)>會轉(zhuǎn)換成std::function<T*(int&&)>。
再加上修改map的接口SetFunc,第一版的動態(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
// 當然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的時候都留了一手,B并不需要知道實際構(gòu)造的是什么,在B的外部,Run()和Run1(),可控制在B里具體要構(gòu)造的對象。
寫完后發(fā)現(xiàn)這東西作用不止于此,下面寫寫一些擴展用法。
寄存參數(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();
}存儲所有構(gòu)造出來的對象
上面的接口返回std::unique_ptr,還要管理對象生命周期,不如更進一步,用factory來管理所有它構(gòu)造的對象,在factory析構(gòu)時統(tǒng)一析構(gòu)。因為我一般寫后臺rpc接口,可以在rpc請求開始時構(gòu)造factory,在構(gòu)造好回包后析構(gòu)factory,這樣在整個請求周期構(gòu)造的對象都在,指針不會失效,而且在請求結(jié)束后可以很方便地進行異步析構(gòu),直接把factory丟到析構(gòu)線程就好。
于是New接口返回值由std::unique_ptr改成T*,同時New可能會造成誤解,改成Get。當然,存儲肯定要用到類型擦除存儲。就成了下面這樣:
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_;
};有個細節(jié)是對于改變過的工廠函數(shù)返回的指針是不應該存在storage_中的,而應該是在工廠函數(shù)中把對象存進storage_。上面的Run1()應該改成這樣:
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)的單例
當返回值由std::unique_ptr改成T*,就可以實現(xiàn)寄存指針了??晌鰳?gòu)的單例指每次請求都重新構(gòu)造,在請求結(jié)束后析構(gòu),但是請求之中只構(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把對象的指針寄存到factory中
factory.Get<C>(factory); // 調(diào)用C構(gòu)造函數(shù)中的func,直接返回寄存的指針,不重復構(gòu)造C
// factory析構(gòu)時C的對象將被析構(gòu)
}裝飾工廠函數(shù),責任鏈工廠
只要再加個接口GetFunc來獲取當前的工廠函數(shù),就可以對工廠函數(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)計調(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();
}用責任鏈模式搞個工廠:
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* {
// 責任鏈模式
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ù)組合
上面的實現(xiàn)要求new T(std::forward(args)...)能合法生成一個T對象指針,在一些情況下很難做到,比如T中有難以初始化的成員,又比如T是一個抽象類:
struct E {
E() = default;
virtual ~E() = default;
virtual void Foo() = 0;
};這樣就要修改Get接口的邏輯,改成如果能合法調(diào)用構(gòu)造函數(shù),就調(diào)用,否則就不調(diào)用。但是這樣放開之后,就各種參數(shù)組合都可以搞了,我覺得這樣可能會很混亂,這邊設置了這個參數(shù)組合,那邊設置了另外的參數(shù)組合,不知道一共設置了哪幾種參數(shù)組合。我覺得還是要加點限制,就規(guī)定參數(shù)組合必須在基類中定義。規(guī)定了一個方法名FactoryGet,所有非構(gòu)造函數(shù)的參數(shù)組合要定義一個靜態(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)用對應的FactoryGet方法,其他參數(shù)組合將會編譯報錯。同時也規(guī)定FactoryGet獲得的指針不存進通用存儲。于是DynamicAbstractFactoryWithStorage就改成這樣:
// new T(std::forward<Args>(args)...)
// T::FactoryGet(std::forward<Args>(args)...)
// 要求上面兩個表達式有且僅有一個合法并且返回T*,Get調(diào)用合法的那個。
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:
// 每個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é)
到此這篇關于C++通用動態(tài)抽象工廠的文章就介紹到這了,更多相關C++通用動態(tài)抽象工廠內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
淺談C語言中的sizeof()和strlen()的區(qū)別
本文主要介紹了C語言中的sizeof()和strlen()的區(qū)別,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05
C++11/14 線程的創(chuàng)建與分離的實現(xiàn)
這篇文章主要介紹了C++11/14 線程的創(chuàng)建與分離的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-01-01
C語言結(jié)構(gòu)體成員賦值的深拷貝與淺拷貝詳解
C語言中的淺拷貝是指在拷貝過程中,對于指針型成員變量只拷貝指針本身,而不拷貝指針所指向的目標,它按字節(jié)復制的。深拷貝除了拷貝其成員本身的值之外,還拷貝成員指向的動態(tài)內(nèi)存區(qū)域內(nèi)容。本文將通過示例和大家詳細說說C語言的深拷貝與淺拷貝,希望對你有所幫助2022-09-09

