Как использовать фабричный шаблон с функторами?

У меня есть набор функторов для вычисления конкретных вещей в диапазонах объектов. По сути, каждый функтор реализует operator():

template <typename Iterator1,
          typename Iterator2> double operator()( Iterator1 it1,
                                                 Iterator2 it2 ) const
{
  return( 0.0 );
}

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

template <class Functor> Foo createFoo( ... // some parameters for foo objects
                                        Functor f = Functor() )
{
  // calculate stuff for "foo" using functor "f"
}

Теперь я хочу делегировать выбор функтора пользователю моей программы, поэтому я решил создать фабрику функторов. Учитывая описательное имя функтора, я хочу создать соответствующий функтор, чтобы его можно было использовать при создании всех объектов Foo, как указано выше.

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

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

Я в тупике здесь и был бы признателен за некоторые комментарии.

ИЗМЕНИТЬ:

Что я собираюсь сделать (неверный код):

DistanceFunctor f = createFunctor( "Bar" ); // Create a functor from a client-supplied string

Foo createFoo( ..., // parameters for foo
               f );

В комментариях также предлагалось использование виртуальных функций. Текущий дизайн функтора, как описано выше, не будет работать с виртуальными функциями, потому что компилятор не может сделать шаблоны функций виртуальными. Настройка класса функтора для использования двух типов Iterator в качестве параметров шаблона возможна, но очень неуклюжа.

ИЗМЕНИТЬ:

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


person Gnosophilon    schedule 08.04.2013    source источник
comment
Можете ли вы показать, как вы собираетесь использовать все эти функции вместе?   -  person Joseph Mansfield    schedule 08.04.2013
comment
Измеримы ли эти накладные расходы на производительность? Вызовы виртуальных функций так сильно мешают, т. е. сказал ли вам профилировщик, что они являются критическим узким местом в производительности? Или вы немного усложняете себе жизнь преждевременной оптимизацией?   -  person Arne Mertz    schedule 08.04.2013
comment
@ArneMertz: я еще не реализовал другой вариант. Я подумал, что если каждый функтор применяется к нескольким миллионам объектов, виртуальные функции могут меня замедлить. Таким образом: Вы можете быть правы :-/ Я реализую другой вариант и отчитаюсь о некоторых результатах.   -  person Gnosophilon    schedule 08.04.2013
comment
@ArneMertz Глупый я: поскольку каждый функтор содержит шаблон функции, я не могу использовать virtual. По крайней мере, не так, как я описал это здесь.   -  person Gnosophilon    schedule 08.04.2013
comment
Что делает DistanceFunctor? Первая предоставленная вами подпись выглядит так, как будто она каким-то образом накапливает значения из диапазона объектов или чего-то подобного. Возможно, вам нужен не функтор, перебирающий диапазон, а предикат, который вы можете передать в createFoo и использовать его в каком-то алгоритме, перебирающем диапазон (например, std::accumulate).   -  person Arne Mertz    schedule 08.04.2013
comment
@ArneMertz Он вычисляет расстояния между диапазонами объектов. Например, один функтор будет вычислять абсолютное расстояние между каждой парой значений, описываемых обоими итераторами. В моей реализации функтор также имеет атрибут, указывающий, сколько элементов следует использовать из обоих диапазонов. Самый близкий пример, который я смог найти, был чем-то вроде L2_simple функтор в FLANN.   -  person Gnosophilon    schedule 09.04.2013


Ответы (1)


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

template <class Iterator>
struct Functor {
  virtual double operator()(Iterator it1, Iterator it2) const = 0;
};

Как это помогает? Наличие шаблона на уровне Functor на первый взгляд выглядит не очень хорошо, так как теперь вам придется знать параметр вне вызова createFunctor, на самом деле вам придется явно указывать тип итератора, если вы его вызываете:

//some concrete functors
template <class It>
struct FuncImpl_Bar : Functor<It> { /* ... implement op()(It, It) ... */ }:

template <class It>
struct FuncImpl_Meow : Functor<It> { /* ... implement op()(It, It) ... */ }:

template <class Iterator>
std::shared_ptr<Functor<Iterator>> createFunctor(std::string const& str)
{
  if (str == "Bar") return std::make_shared<FuncImpl_Bar<Iterator>>(/* args *);
  else return return std::make_shared<FuncImpl_Meow<Iterator>>(/* args *);
}

//...
auto MyFunctor = createFunctor<???>("Bar"); //what iterator type???

Но AFAICS вам не нужно знать точный тип функтора вне createFoo - достаточно, если createFoo знает этот тип:

Foo createFoo( ... // some parameters for foo objects
                                        std::string const& str ) //look mum, no templates!
{
  typedef whatever_the_container_i_use::iterator Iter;
  Functor<Iter> MyFunctor = createFunctor<Iter>(str);
  // calculate stuff for "foo" using functor "f"
}

//...
auto myFoo = createFoo(args, "Bar");
auto myOtherFoo = createFoo(args, "Moo");

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

person Arne Mertz    schedule 09.04.2013
comment
Спасибо, это хорошее решение. Я сейчас думаю о том, чтобы переписать этот дизайн, так как ваше решение ясно продемонстрировало некоторые недостатки в моем первоначальном дизайне. Спасибо за ваш вклад :) - person Gnosophilon; 15.04.2013