c/c++语言开发共享C++通用动态抽象工厂的实现详解

背景一开始,我是想到了下面这个场景:struct a { void foo() {}};struct b { void bar() { a().foo(); }};如上面代码,b的bar中

背景

一开始,我是想到了下面这个场景:

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

(0)
上一篇 2022年7月12日
下一篇 2022年7月12日

精彩推荐