Как сделать так, чтобы заголовок фабрики не зависел от созданных шаблонных объектов? - PullRequest
4 голосов
/ 14 сентября 2010

У меня есть абстрактный базовый класс, например:

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.

Есть ли способ построить это для достижения этой цели?(Помимо ручного создания новой функции Create в MyFactory для каждого типа, который я хочу, вместо того, чтобы шаблонировать его).

Ответы [ 6 ]

5 голосов
/ 14 сентября 2010

вам нужно поместить фабричную реализацию в файл реализации (о котором вы упомянули, вы хотели бы избежать, но это меньшее зло, если интерфейсы не маленькие, и / или ваши проекты невелики).

Конечно, есть несколько других способов, которыми вы могли бы подойти к этому, таких как помещение реализации в базовые классы и создание производных базовых фабрик или использование какого-то другого действительно странного синтаксиса шаблонов для сокращения случаев создания экземпляров в зависимых переводах. это действительно сводится к удобству и масштабу для вашего проекта. если вы работаете над одним или несколькими крупными проектами, то полная абстракция по отношению к реализации в долгосрочной перспективе лучше всего будет отвечать вашим потребностям (при условии, что вам нужен динамический полиморфизм и память).

Вы также можете попробовать другие подходы (например, перегрузку), чтобы уменьшить количество ошибок, используя безопасность типов.

короткий ответ заключается в том, что вам действительно нужно абстрагировать интерфейсы / экземпляры в один или несколько файлов реализации, чтобы удалить зависимости заголовка - очень распространенная идиома и множество способов ее решения. со временем вы также можете разделить и использовать полиморфизм для своих фабрик.

вы также можете использовать шаблоны форвардных объявлений, чтобы минимизировать наборы для модуля компиляции. при условии:

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

используя это, вы можете выбрать части программ / интерфейсов, которые вам нужны в библиотеке, а затем обернуть все это в реальном Factory (для сборки под рукой). компоновщик / создание экземпляра должно произойти сбой по пути, если вы действительно пытаетесь запросить создание, которое не видно.

Есть много вариантов, на самом деле - вам нужно выяснить, насколько велика ваша шкала, чтобы определить, что абстрагировать (или нет). для создания экземпляра требуется интерфейс, чтобы удалить зависимости заголовка, вам нужно где-то абстрагировать реализацию.

1 голос
/ 14 сентября 2010

Мой подход к той же проблеме в прошлом заключался в создании набора конкретных фабрик (по одному на тип), которые регистрируются на глобальной фабрике (в целях иллюстрации, индексация по имени объекта):

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.

1 голос
/ 14 сентября 2010

Вы можете использовать явную реализацию шаблона.Попытка вызова фабричного метода с явно заданным параметром шаблона приведет к ошибке компоновщика.Обратите внимание на явную реализацию шаблона в 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:

#include "AbstractBaseClass.h"

template<class T>
class ConcreteClass : public AbstractBaseClass
{
public:
   ConcreteClass(T input) : data(input) {}
private:
    T data;
};
0 голосов
/ 10 января 2016

Я понимаю, что отвечаю на это пять лет спустя. Возможно, с тех пор язык немного вырос. Я хотел бы предложить что-то, что кажется правильным, если я правильно понимаю вопрос, если только для того, чтобы помочь другим, которые могут найти этот вопрос и задаться вопросом, что они могут сделать.


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). Я думаю, что это то, что хотел ОП, но без необходимости добавлять различные функции создания в каждый класс, который может генерировать эта фабрика.

Я также показал, как не нужно явно создавать функции для каждого класса, который должна создавать фабрика. Перегруженные фабрикой функции создания могут создавать все что угодно из базового класса, соответствующего соответствующей сигнатуре.

Я не проводил подробный анализ производительности этого кода, но сделал достаточно, чтобы убедиться, что большая часть работы выполняется в операторе потоковой передачи. Это компилируется примерно за 1 секунду на моем четырехъядерном компьютере с частотой 3,30 ГГц. Возможно, вам придется поэкспериментировать с более надежным кодом, чтобы увидеть, насколько сильно он может затормозить компилятор, если вообще сильно.

Я тестировал этот код в VC ++ 2015, хотя, вероятно, он довольно легко работает в других компиляторах. Если вы хотите скопировать это, вам нужно добавить свои собственные защитные заголовки. В любом случае, я надеюсь, что это полезно.

0 голосов
/ 14 сентября 2010

Это невозможно, потому что ConcreteClass - это шаблон, а это значит, что вам нужна полная реализация, доступная во время компиляции.По той же причине, по которой вы не можете предварительно скомпилировать шаблоны и должны записать их все в заголовочные файлы.

0 голосов
/ 14 сентября 2010

Вы ищете идиому "PIMPL".Хорошее объяснение есть на сайте GOTW Херба Саттера

...