背景
一开始,我是想到了下面这个场景:
struct a { void foo() {} }; struct b { void bar() { a().foo(); } };
如上面代码,b的bar中构造了a,然后调用了a的foo函数。假如我想在别的场景中复用b的bar,但是这时a的foo就不符合需求了,需要定制,于是我们想到可以用多态,把a的foo改成虚函数,然后写出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的工厂函数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中还要构造别的c、d等类,难道我们要为每个类都写一个工厂函数吗?这成本也太高了,而且可能大部分类都只有一个,不用搞继承,写工厂函数就是无用功。那么,有没有一种通用的方式可以在写b代码的时候就对a、c、d等类的构造都留一手,使得以后可以由b外的代码控制实际构造的是a1等,而不需要修改b的代码?这就是我写动态抽象工厂的原始需求。
实现
思路很简单,就是为每个类都自动生成一个工厂函数就好了,然后在b中不直接构造a、c、d等对象,而是都调用对应类的工厂函数。然后这个自动生成的工厂默认就是构造原始的类的对象,比如a,也有接口可以改成生成子类,比如a1。对每个类生成一个工厂函数自然就想到用模板了。至于这个接口怎么实现,就有两大分支,分别是编译期和运行期。编译期一般就是用模板特化了。我觉得运行期会更有趣,用法会更多,就选了运行期。
运行期的意思就是要搞个变量来存下这个修改的工厂函数,自然就想到用std::function。当然免不了的是要把这个变量传给b。如果管理a的是一个变量,管理c、d的是另外两个变量,那就要传很多很多变量给b,这样也太繁琐了,所以应该一个变量存下所有类的工厂函数,然后把这个变量传遍所有需要使用工厂函数的对象或函数当中。所以这个变量的类型是一个确定的类型,不能带模板(或者说模板参数不能跟工厂对应的类相关,如a、c、d等)。那么模板就放到方法当中了。很自然地,这个类型的接口就应该是这样:
struct dynamicabstractfactory { template <typename t, typename... args> std::unique_ptr<t> new(args&&...); };
这里插一段,为什么叫动态抽象工厂呢?按照我的理解,工厂模式就是实现一个返回t*的函数f,里面用ifelse来控制最终返回的是t还是t的某个子类。抽象工厂模式就是连这个函数f都是可变的。动态是指这个f是运行时可变。
那么这个接口怎么实现呢?我的想法是用一个map来记录类型组合(t, args&&…)到工厂函数std::function<t*(args&&…)>的映射,并存储std::function<t*(args&&…)>。new的实现就是查找map中有没有对应的工厂函数,有就调用工厂函数,没有就调用t本身的构造函数。当然,也需要提供一个接口来修改这个map。
要实现这个map还有三个细节:
- 存储的std::function<t*(args&&…)>是不同的类型,需要用类型擦除存储。如果可用c++17的话可直接用std::any,但我的环境有些老代码用gcc7编不过,所以还是只能用c++11,于是用std::shared_ptr<void>来代替(我一开始还是用std::unique_ptr+虚析构函数+继承来实现的,后来才知道std::shared_ptr自带这个功能)。
- map的key是一个类型组合,就用std::type_index吧。由于std::function<t*(args&&…)>已经把整个类型组合包进去了,而且一定会实例化,就直接用它吧。于是key就成了std::type_index(typeid(std::function<t*(args&&…)>))。
- 由于接口new(args&&…)的每个参数类型args&&都是引用类型,为了保持一致性,为了map能找到正确的函数,要求std::function中的每个参数也是引用类型,所以上面都写作std::function<t*(args&&…)>。比如std::function<t*(int)>会转换成std::function<t*(int&&)>。
再加上修改map的接口setfunc,第一版的动态抽象工厂就做好了:
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_; }; // 旧环境,用原始a、c、d // 当然b也可以用factory来生成 void run() { dynamicabstractfactory factory; factory.new<b>(factory)->bar(); } // 新环境,用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在构造a、c、d的时候都留了一手,b并不需要知道实际构造的是什么,在b的外部,run()和run1(),可控制在b里具体要构造的对象。
写完后发现这东西作用不止于此,下面写写一些扩展用法。
寄存参数
子类的构造函数的参数可以跟父类不一样,通过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(); }
存储所有构造出来的对象
上面的接口返回std::unique_ptr,还要管理对象生命周期,不如更进一步,用factory来管理所有它构造的对象,在factory析构时统一析构。因为我一般写后台rpc接口,可以在rpc请求开始时构造factory,在构造好回包后析构factory,这样在整个请求周期构造的对象都在,指针不会失效,而且在请求结束后可以很方便地进行异步析构,直接把factory丢到析构线程就好。
于是new接口返回值由std::unique_ptr改成t*,同时new可能会造成误解,改成get。当然,存储肯定要用到类型擦除存储。就成了下面这样:
class generalstorage { public: generalstorage(size_t reserve_size = 256) { storage_.reserve(reserve_size); } ~generalstorage() { // 保证按添加顺序逆序析构 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_; };
有个细节是对于改变过的工厂函数返回的指针是不应该存在storage_中的,而应该是在工厂函数中把对象存进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(); }
寄存指针,可析构的单例
当返回值由std::unique_ptr改成t*,就可以实现寄存指针了。可析构的单例指每次请求都重新构造,在请求结束后析构,但是请求之中只构造一次。看下面例子:
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); // 构造c,并通过setfunc把对象的指针寄存到factory中 factory.get<c>(factory); // 调用c构造函数中的func,直接返回寄存的指针,不重复构造c // factory析构时c的对象将被析构 }
装饰工厂函数,责任链工厂
只要再加个接口getfunc来获取当前的工厂函数,就可以对工厂函数玩装饰模式了。
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; } };
统计调用次数:
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的结果是std::fuction<d*(int&&)>类型的,这里经过了一次类型转换 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)); } // 构造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)); } // 构造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 }
允许构造函数之外的参数组合
上面的实现要求new t(std::forward(args)…)能合法生成一个t对象指针,在一些情况下很难做到,比如t中有难以初始化的成员,又比如t是一个抽象类:
struct e { e() = default; virtual ~e() = default; virtual void foo() = 0; };
这样就要修改get接口的逻辑,改成如果能合法调用构造函数,就调用,否则就不调用。但是这样放开之后,就各种参数组合都可以搞了,我觉得这样可能会很混乱,这边设置了这个参数组合,那边设置了另外的参数组合,不知道一共设置了哪几种参数组合。我觉得还是要加点限制,就规定参数组合必须在基类中定义。规定了一个方法名factoryget,所有非构造函数的参数组合要定义一个静态factoryget方法,方法返回t*,比如:
struct e { e() = default; static e* factoryget(int) { return nullptr; } virtual ~e() = default; virtual void foo() = 0; };
这样get接口的逻辑就可以改成如果能合法调用构造函数,就调用,否则就调用对应的factoryget方法,其他参数组合将会编译报错。同时也规定factoryget获得的指针不存进通用存储。于是dynamicabstractfactorywithstorage就改成这样:
// new t(std::forward<args>(args)...) // t::factoryget(std::forward<args>(args)...) // 要求上面两个表达式有且仅有一个合法并且返回t*,get调用合法的那个。 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* }
总结
到此这篇关于c++通用动态抽象工厂的文章就介绍到这了,更多相关c++通用动态抽象工厂内容请搜索<计算机技术网(www.ctvol.com)!!>以前的文章或继续浏览下面的相关文章希望大家以后多多支持<计算机技术网(www.ctvol.com)!!>!
需要了解更多c/c++开发分享C++通用动态抽象工厂的实现详解,都可以关注C/C++技术分享栏目—计算机技术网(www.ctvol.com)!
本文来自网络收集,不代表计算机技术网立场,如涉及侵权请联系管理员删除。
ctvol管理联系方式QQ:251552304
本文章地址:https://www.ctvol.com/c-cdevelopment/1120382.html