在工厂模式中自动注册派生类

时间:2015-12-04 16:15:32

标签: c++ c++11 factory

对于我正在编写的应用程序,一个重要的部分是用户可以为不同的进程选择任何可用选项。所有这些选项都派生自相同的基类。我偶尔会添加新的选项,我想让这个过程尽可能简单。因此,在浏览网络和SO之后,我就是这样:

基类:

class Base {
    double some_member;
    virtual double some_method() = 0;
};

派生类:

class Derived : Base {
//...
};

用于保存可用类型表并根据指定名称创建派生类的工厂:

template <typename B>
class Factory{
public:
    template <typename D>
    void registerType(std::string name)
    {
        static_assert(std::is_base_of<B, D>::value, "class doesn't derive from the base");
        table_[name] = &createFunc<D>;
    }
    B* create(std::string name)
    {
        const auto it = table_.find(name);
        if(it != table_.end())
            return it->second();
        FILE_LOG(logERROR) << "unidentified option, acceptable options are:";
        for(auto const &m : list())
            FILE_LOG(logERROR) << '\t' << m;
        return nullptr;
    }

    std::vector<std::string> list()
    {
        std::vector<std::string> lst;
        for(auto const &iter : table_)
            lst.push_back(iter.first);
        return lst;
    }
private:
    template<typename D>
    static B* createFunc()
    {
        return new D();
    }
    typedef B* (*PCreateFunc)();
    std::map<std::string, PCreateFunc> table_;
};

包含特定流程的所有选项的类:

class OptsContainer {
private:
    std::vector<std::unique_ptr<Base>> opts_;
public:
    //some other stuff
    void addOption(const std::string &); //adds a new option with given name
    static Factory<Base> factory;
};

Factory<Base> OptsContainer::factory;

void OptsContainer::addOption(const std::string &name)
{
    opts_.push_back(std::unique_ptr<Base>(factory.create(name)));
}

此时,每当我添加一个新的派生类时,我只需要在工厂表中注册该类:

///All older includes
#include "derived42.h"

void initOptions()
{
    //all other registrations
    OptsContainer::factory.registerType<Derived42>("Derived42");
}

我在节目开始时拨打initOptions。这很有效,但我觉得应该可以做得更好。我想要的是只需添加#include "derived42"和类型来注册自己,因此不需要initOptions函数并在一开始就调用它。

我是否必须使用Boost / Loki / ...(如另一篇文章中所述)?这甚至可以在vanilla c ++中使用吗?或者我现在应该和我拥有的一样生活?仅供参考,我使用Visual Studio Express 2015编写程序,但我也可以将代码移到Linux(gcc)。换句话说,不喜欢使用VS2015中未实现的c ++ 11 / c ++ 14模式,但如果需要,我可以轻松地将所有内容移动到gcc。

1 个答案:

答案 0 :(得分:1)

你的代码对我来说太复杂了。也许我错过了一个原因。

你无法避免注册。您正在寻找的是在编译时注册,我认为这是不可能的。由于您在编译时寻找注册,这意味着您不需要在接口中公开注册方法。因此,您的客户端接口应该由基类和创建方法组成:

// { client interface header
class B
{
public:
  virtual ~B() = 0 {}
  //...
};

B* Create( const char* s );
// } client interface header

一个简单的创建功能:

// { client interface implementation

template< typename T >
B* New()
{
  return new T;
}

typedef B* (*PFNew)();

B* Create( const char* s )
{
  typedef std::map< string, PFNew > Map;

  static Map m;
  if ( ! m.size() )
  {
    m[ D1::Signature() ] = New< D1 >;
    m[ D2::Signature() ] = New< D2 >;
    //...
  }

  Map::const_iterator ci = m.find( s );
  if ( ci == m.end() )
    throw -1;
  return ci->second();
}

// } client interface implementation