如何使Factory的Header不依赖于它创建的模板化对象?

时间:2010-09-14 04:28:01

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

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

class AbstractBaseClass
{}; 

从中衍生出来的模板化具体类:

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

AndI有一个创建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函数,而不是模板化它。)

6 个答案:

答案 0 :(得分:5)

你需要将工厂实现放入实现文件中(你提到过你想要避免的,但除非接口很小,和/或你的项目很小,否则它是较小的邪恶)。

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

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

简短的回答是,你真的需要将接口/实例化抽象为一个或多个实现文件,以删除标头依赖关系 - 非常常见的习惯用法,以及解决它的许多方法。你可以在工程中进一步划分和使用多态性。

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

/** 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) {
    /* … */
}

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

有很多选择,实际上 - 您需要弄清楚您的规模有多大,以确定抽象(或不抽象)。实例化需要接口,要删除标头依赖关系,你必须在某个地方抽象实例化。

答案 1 :(得分:1)

您可以使用显式模板实例化。尝试使用未明确instanciated的模板参数调用工厂方法将导致链接器错误。请注意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;
};

答案 2 :(得分:1)

我过去遇到同样问题的方法是创建一组在全球工厂注册的具体工厂(每种类型一个)(为了便于说明,按对象名称编制索引):

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 :(得分:0)

你正在寻找“PIMPL”的习语。 Herb Sutter's GOTW site

有一个很好的解释

答案 4 :(得分:0)

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

答案 5 :(得分:0)

我意识到我五年后才回答这个问题。也许从那以后语言已经有点发展了。如果我能正确理解这个问题,我愿意提供一些似乎正确的事情,如果没有其他意义,那就是帮助那些可能会发现这个问题的人,并想知道他们能做些什么。

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所要求的那样。 / p>

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

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

我没有对此代码进行广泛的性能分析,但我做得足以看到大部分工作都在流媒体运营商中进行。这在我的3.30Ghz四核机器上编译大约1秒钟。您可能需要尝试使用更强大的代码来查看它可能会对编译器造成多大的影响,如果有的话。

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