【问题标题】:How do I make my Factory's Header not dependent on the templated objects it creates?如何使我的工厂标题不依赖于它创建的模板化对象?
【发布时间】:2011-04-11 23:48:21
【问题描述】:

我有一个像这样的抽象基类:

class AbstractBaseClass
{}; 

派生自它的模板化具体类:

template<class T>
class ConcreteClass : public AbstractBaseClass
{
public:
   ConcreteClass(T input) : data(input) {}
private:
    T data;
};

我有一个创建 AbstractBaseClasses 的工厂类

class MyFactory
{
public:
   boost::shared_ptr<AbstractBaseClass> CreateBlah();
   boost::shared_ptr<AbstractBaseClass> CreateFoo();

   template<class T>
   boost::shared_ptr<AbstractBaseClass> Create(T input)
   {
      return boost::shared_ptr<AbstractBaseClass>(new ConcreteClass<T>(input));
   }
};

问题在于,现在所有使用 MyFactory 的东西都必须包含 ConcreteClass 的整个实现。理想情况下,我只希望 MyFactory 了解 ConcreteClass。

有没有什么方法可以构建这个来实现这个目标? (除了在 MyFactory 中为我想要的每种类型手动创建一个新的 Create 函数,而不是对其进行模板化)。

【问题讨论】:

  • ConcreteClass 是可见的,但您可以将其设为 private 以降低其可访问性。完全对用户隐藏它值得麻烦吗?
  • 我在一个非常大的代码库中,考虑减少头文件依赖和编译时间等对我们来说很重要,所以这就是为什么我要考虑这个问题。

标签: c++ templates design-patterns dependencies factory


【解决方案1】:

您需要将工厂实现放入实现文件中(您提到过您希望避免这种情况,但除非接口很小,和/或您的项目很小,否则它的危害较小)。

当然,还有其他一些方法可以解决这个问题,例如将实现放入基类中,并创建派生的基类工厂,或者使用其他一些非常奇怪的模板语法来减少依赖翻译中的实例化。这真的归结为您的项目的便利性和规模。如果您正在处理一个或多个大型项目,那么从长远来看,完全抽象 wrt 实例化将最能满足您的需求(假设您需要动态多态性和内存)。

您也可以尝试其他方法(例如重载),通过使用类型安全来减少错误。

简短的回答是,您确实需要将接口/实例化抽象为一个或多个实现文件以删除头文件依赖项——这是非常常见的习惯用法,并且有多种解决方法。当然,您也可以进一步划分并为您的工厂使用多态性。

您也可以使用模板前向声明来最小化编译单元的集合。提供:

/** in MyIntermediateFactory.hpp */
class MyIntermediateFactory {
public:
    static template<class T> boost::shared_ptr<T> Create(float);
};

/** in Concrete.hpp */
template<Concrete>
boost::shared_ptr<T> MyIntermediateFactory::Create<Concrete>(float arg) {
    /* … */
}

使用它,您可以在库中选择您需要的程序/接口部分,然后将它们全部包装在一个真正的工厂中(用于手头的构建)。如果您实际上尝试请求不可见的创建,则链接器/实例化应该会失败。

确实有很多选择 - 你需要弄清楚你的规模有多大才能确定要抽象(或不抽象)什么。实例化需要接口,要移除头依赖,你必须在某个地方抽象化实例化。

【讨论】:

    【解决方案2】:

    我过去解决相同问题的方法是创建一组在全局工厂中注册的具体工厂(每种类型一个)(出于说明目的,按对象名称进行索引):

    class AbstractBaseClass;
    class ConcreteFactory
    {
    public:
       AbstractBaseClass * create();
    };
    class AbstractFactory 
    {
    public:
       void registerFactory( std::string const & name, std::shared_ptr<ConcreteFactory> const & f )
       {
          factory[ name ] = f; // check for collisions, complain if so ...
       }
       AbstractBaseClass * create( std::string const & name )
       {
          return factory[name]->create(); // check for existence before dereferencing...
       }
    private:
       std::map<std::string, std::shared_ptr<ConcreteFactory> > factory;
    };
    

    我在一段大量模板化的代码中使用了它以减少编译时间。每个具体工厂和它创建的类只需要在一个注册具体工厂的翻译单元中。其余代码只需要使用AbstractBaseClass的通用接口即可。

    【讨论】:

      【解决方案3】:

      您可以使用显式模板实例化。尝试使用未显式实例化的模板参数调用工厂方法会给您一个链接器错误。注意 MyFactory.cpp 中的显式模板实例化

      template AbstractBaseClass* MyFactory::Create(int input);
      

      所有放在一起看起来像这样(为简单起见,我删除了 shared_ptr):

      Main.cpp:

      #include "AbstractBaseClass.h"
      #include "MyFactory.h"
      
      //we do not need to know nothing about concreteclass (neither MyFactory.h includes it)    
      
      int main()
      {
          MyFactory f;
          AbstractBaseClass* ab = f.Create(10);
          ab = f.Create(10.0f);
          return 0;
      }
      

      MyFactory.h:

      #include "AbstractBaseClass.h"
      
      class MyFactory
      {
      public:
      
         template<class T>
         AbstractBaseClass* Create(T input);
       };
      

      MyFactory.cpp:

      #include "MyFactory.h"
      #include "ConcreteClass.h"
      
      template<class T>
      AbstractBaseClass* MyFactory::Create(T input) {
          return new ConcreteClass<T>(input);
      }
      
      //explicit template instanciation
      template AbstractBaseClass* MyFactory::Create(int input);
      
      //you could use as well specialisation for certain types
      template<>
      AbstractBaseClass* MyFactory::Create(float input) {
          return new ConcreteClass<float>(input);
      }
      

      AbstractBaseClass.h:

      class AbstractBaseClass{};
      

      ConcreteClass.h:

      #include "AbstractBaseClass.h"
      
      template<class T>
      class ConcreteClass : public AbstractBaseClass
      {
      public:
         ConcreteClass(T input) : data(input) {}
      private:
          T data;
      };
      

      【讨论】:

      • 有趣,不知道我能做到这一点。
      • 不幸的是,没有任何编译器(除了 Comeau 的那个)可以编译 MyFactory.h。您根本无法声明模板化函数并在另一个文件中定义它。它确实在标准中,但没有人支持此功能。
      • @grim 使用 gcc-4.4 编译(和工作)的代码将使用 Visual Studio 进行检查...
      • @grim 看到:codeguru.com/cpp/com-tech/atl/tutorials/article.php/c3617 - 它指出 Visual Studio 6 不能很好地支持显式模板实例化
      【解决方案4】:

      您正在寻找“PIMPL”成语。 Herb Sutter's GOTW site 有很好的解释

      【讨论】:

      • 我不确定 PImpl 将如何帮助我。由于我正在处理模板,因此在编译时需要实现。由于 Factory 的 create 方法是模板化的,因此需要从头文件中调用 PImpl 的函数。如果我遗漏了什么,请您说的更具体些吗?
      • 它会减少对接口的依赖,但不会完全删除它。要完全免费,您需要使用注册表类型的方法,工厂直到运行时才知道它正在加载什么。这种事情在插件架构中很常见,工厂读取注册表以了解如何创建某些东西。它有点毛茸茸,但当你让它工作时非常酷。
      • 如果您的实现依赖于未用源代码编写的内容,您将失去编译器静态检查。
      • @YeenFei:不完全——C++ 中的插件体系结构中的插件和插件都依赖于一个公共头文件,尽管它们没有一起编译。虽然这确实为人为错误留下了空间,但只要两个编译使用相同的文件,您就可以对代码进行静态编译时检查。如果您的注册系统依赖于配置文件,则可能仅存在运行时错误,但您的所有代码仍会得到正确检查。
      【解决方案5】:

      无法完成,因为 ConcreteClass 是一个模板,这意味着您需要在编译时提供完整的实现。同样的原因,您不能预编译模板而必须将它们全部写入头文件。

      【讨论】:

      • 你可以 - 通过显式模板实例化
      【解决方案6】:

      我意识到五年后我会回答这个问题。也许从那时起这种语言已经发展了一点。如果我能正确理解这个问题,我想提供一些看起来正确的东西,如果只是为了帮助其他可能发现这个问题并想知道他们能做什么的人。


      factory.hpp

      #include "base.hpp"
      
      namespace tvr
      {
          namespace test
          {
              class factory
              {
              public:
                  typedef base::ptr Ptr;
      
                  enum eSpecial
                  {
                      eDerived
                  };
      
                  template<typename Type>
                  Ptr create()
                  {
                      Ptr result;
                      result.reset(new Type());
                      return result;
                  }
      
                  template<typename Type, typename DataType>
                  Ptr create(const DataType& data)
                  {
                      Ptr result;
                      result.reset(new Type(data));
                      return result;
                  }
      
                  template<typename Type, typename DataType>
                  Ptr create(const DataType& data, eSpecial tag)
                  {
                      Ptr result;
                      result.reset(new Type());
                      static_cast<Type*>(result.get())->set_item(data);
                      return result;
                  }
              };
          }
      }
      

      base.hpp

      #include <memory>
      
      namespace tvr
      {
          namespace test
          {
              class base
              {
              public:
                  typedef std::shared_ptr<base> ptr;
      
              public:
                  base() {}
                  virtual ~base() {}
                  virtual void do_something() = 0;
              };
          }
      }
      

      some_class.hpp

      #include <ostream>
      
      namespace tvr
      {
          namespace test
          {
              struct some_class
              {
              };
          }
      }
      
      std::ostream& operator<<(std::ostream& out, const tvr::test::some_class& item)
      {
          out << "This is just some class.";
          return out;
      }
      

      template_derived.hpp

      #include <iostream>
      
      #include "base.hpp"
      
      namespace tvr
      {
          namespace test
          {
              template<typename Type>
              class template_derived : public base
              {
              public:
                  template_derived(){}
                  virtual ~template_derived(){}
                  virtual void do_something()
                  {
                      std::cout << "Doing something, like printing _item as \"" << _item << "\"." << std::endl;
                  }
      
                  void set_item(const Type data)
                  {
                      _item = data;
                  }
              private:
                  Type _item;
              };
          }
      }
      

      最后是 main.cpp

      #include <vector>
      
      #include "base.hpp"
      #include "factory.hpp"
      
      namespace tvr
      {
          namespace test
          {
              typedef std::vector<tvr::test::base::ptr> ptr_collection;
      
              struct iterate_collection
              {
                  void operator()(const ptr_collection& col)
                  {
                      for (ptr_collection::const_iterator iter = col.begin();
                          iter != col.end();
                          ++iter)
                      {
                          iter->get()->do_something();
                      }
                  }
              };
          }
      }
      
      #include "template_derived.hpp"
      #include "some_class.hpp"
      
      namespace tvr
      {
          namespace test
          {
              inline int test()
              {
                  ptr_collection items;
      
                  tvr::test::factory Factory;
      
                  typedef template_derived<unsigned int> UIntConcrete;
                  typedef template_derived<double> DoubleConcrete;
                  typedef template_derived<std::string> StringConcrete;
                  typedef template_derived<some_class> SomeClassConcrete;
      
                  items.push_back(Factory.create<SomeClassConcrete>(some_class(), tvr::test::factory::eDerived));
                  for (unsigned int i = 5; i < 7; ++i)
                  {
                      items.push_back(Factory.create<UIntConcrete>(i, tvr::test::factory::eDerived));
                  }
                  items.push_back(Factory.create<DoubleConcrete>(4.5, tvr::test::factory::eDerived));
                  items.push_back(Factory.create<StringConcrete>(std::string("Hi there!"), tvr::test::factory::eDerived));
      
                  iterate_collection DoThem;
                  DoThem(items);
      
                  return 0;
              }
          }
      }
      
      int main(int argc, const char* argv[])
      {
          tvr::test::test();
      }
      

      输出

      Doing something, like printing _item as "This is just some class.".
      Doing something, like printing _item as "5".
      Doing something, like printing _item as "6". 
      Doing something, like printing _item as "4.5".
      Doing something, like printing _item as "Hi there!".
      

      这使用模板、函数重载和通过枚举标记的组合来帮助创建一个灵活的工厂类,它不需要了解它实例化的各个类,包括 OP 询问的模板化具体类。

      “eDerived”标签(以枚举的形式)告诉编译器使用工厂的 create 函数版本,该函数采用像 template_derived 类这样的类,它有一个函数允许我将数据分配给其中一个其成员。从我在 main.cpp 中订购标头的方式可以看出,工厂对 template_derived 一无所知。调用基类的虚函数(do_something)的函数也没有。我认为这是 OP 想要的,但不必在该工厂可能生成的每个类中添加各种创建函数。

      我还展示了如何不必为工厂应该创建的每个类显式创建函数。工厂的重载创建函数可以创建从基类派生的任何与适当签名匹配的东西。

      我没有对这段代码进行广泛的性能分析,但我已经做了足够多的工作来发现大部分工作都发生在流式操作符中。在我的 3.30Ghz 四核机器上编译大约需要 1 秒。您可能需要尝试使用更健壮的代码,以了解它可能会使编译器陷入多么严重的困境(如果有的话)。

      我已经在 VC++ 2015 中测试了这段代码,尽管它可能很容易在其他编译器中工作。如果要复制此内容,则需要添加自己的保护标头。无论如何,我希望这是有用的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-20
        • 1970-01-01
        • 1970-01-01
        • 2011-09-10
        • 1970-01-01
        • 2015-08-25
        • 1970-01-01
        • 2018-04-05
        相关资源
        最近更新 更多