С++. Шаблоны и информация о пользователях во время выполнения

Я пытался узнать больше об универсальном программировании, так как мне кажется, что я недостаточно разбираюсь в этом. Итак, я думаю о том, как реализовать шаблонную версию одной из моих программ. Программа, с которой я пытаюсь это сделать, представляет собой программу числового интегратора, в которой пользователь выбирает, какой интегратор использовать (т. е. Эйлера, Рунге-Кутта и т. д.), а затем интегрирует любую функцию по своему выбору. Мой текущий метод сделать это состоит в том, чтобы иметь абстрактный базовый класс Integrator и несколько производных классов, которые реализуют метод интеграции. Таким образом, код будет выглядеть примерно так (происходит гораздо больше, но это просто для демонстрации методологии). Обратите внимание, что я использую для этого Qt и объявляю Integrator *integrator; в классе MainWindow.

void MainWindow::on_integrateButton_clicked() {
string whichIntegrator = getUserChoice();

integrator = getIntegrator( whichIntegrator, whichFunction, order );
integrator->setUp( data ); // things like initial conditions, time, step size, etc...

runIntegratorInNewThread();
}

с getIntegrator, по существу, используя фабричный метод

// x being current data, xdot being the results of evaluating derivatives
typedef void (*pFunction)(const double t, const double x[], double *xdot);

Integrator* getIntegrator( const string &whichIntegrator, pFunction whichFunction, int order  ) {
    if (whichIntegrator == "Euler") {
        return new Euler(whichFunction, order);
    } else if (whichIntegrator == "RungeKutta") {
        return new RungeKutta(whichFunction, order);
    }
}

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


person Muckle_ewe    schedule 28.03.2013    source источник
comment
Не пытайтесь применять шаблоны к случайным ситуациям. Они чрезвычайно полезны в определенных сценариях, но я не думаю, что это один из них. Просто сохраните динамический полиморфизм с использованием виртуальных функций, если он работает нормально.   -  person mfontanini    schedule 28.03.2013
comment
Спасибо за ответ. Я предполагаю, что я видел несколько кодов интегратора, все используемые шаблоны. Однако разница с ними заключалась в том, что это было больше похоже на «открыть файл cpp и отредактировать там что-то, чтобы запустить его», тогда как у меня есть графический интерфейс со всеми видами наворотов. Мне просто было интересно посмотреть, можно ли это сделать по шаблону. Мое текущее использование шаблонов распространяется на написание алгоритмов для контейнеров, и я уверен, что мог бы использовать их не только для этого. В конце концов, интеграция — это своего рода алгоритм, так что, возможно, я мог бы создать его по шаблону. Однако здесь лучше придерживаться виртуальных функций.   -  person Muckle_ewe    schedule 28.03.2013


Ответы (2)


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

Можно ли это сделать с помощью шаблонов? Ответ «Да», и это выглядит так, используя C++11 и shared_ptr:

template<class T>
std::shared_ptr<T> getIntegrator(pFunction whichFunction, int order)
{
    return std::make_shared<T>(whichFunction, order);
}

И в вашем абоненте:

std::shared_ptr<Integrator> integrator;
if (whichIntegrator  == "Euler")
{
    integrator = getIntegrator<Euler>(whichFunction, order);
}
else if(whichIntegrator  == "RungeKutta")
{
    integrator = getIntegrator<RungeKutta>(whichFunction, order);
}

Еще одно замечание: вы должны быть очень осторожны с утечками памяти здесь, вы обновляетесь и возражаете, и если вы никогда не выпускаете ее, у вас будет утечка.

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

person Kyle C    schedule 28.03.2013
comment
Как это могло работать? Предполагается, что which указывает, какой интегратор выделить. Итак, каким будет T, который вы должны явно указать? - person mfontanini; 28.03.2013
comment
Кроме того, я согласен с тем, что здесь нельзя использовать шаблоны, но он спросил, можно ли, и я показал пример. - person Kyle C; 28.03.2013
comment
Тогда ответ должен быть не используйте здесь шаблоны. - person mfontanini; 28.03.2013
comment
getIntegrator точно такой же, как make_shared. - person mfontanini; 28.03.2013
comment
Я решил проголосовать за этот ответ, так как он имел для меня наибольший смысл, как и то, о чем я думал. - person Muckle_ewe; 28.03.2013

Допустим, я захотел написать всю эту систему со статически типизированными интеграторами.

Я бы взял ваш on_integrateButton_clicked и изменил его на что-то вроде этого:

void MainWindow::on_integrateButton_clicked() {
  string whichIntegrator = getUserChoice();

  runInNewThread( [whichIntegrator,whichFunction,order]() {
    struct functor {
      FunctionType func;
      OrderType order;
      functor( FunctionType func_in, OrderType order_in):func(std::move(func_in)), order(std::move(order_in)) {}
      template<typename Integrator>
      void operator()( Integrator* integrator ) {
        // code using integrator here, not a virtual interface to it, an actual instance of the final type
      }
    };
    RunWithChosenIntegrator( whichIntegrator, functor(whichFunction,order) );
  } );
}

Как видите, код выглядит немного задом наперед.

Мы откладываем выбор типа до тех пор, пока это возможно, и в этот момент мы вызываем functor с указателем на интегратор. Это означает, что код, использующий интегратор, имеет полную информацию о типе интегратора и не работает с ним абстрактно.

Однако обычно для таких проблем достаточно полиморфизма во время выполнения или стирания типов.

Теперь RunWithChosenIntegrator немного странный зверь. У него будет такая подпись:

template<typename Functor>
void RunWithChosenIntegrator( std::string const&whichIntegrator, Functor&& func ) {
  if (whichIntegrator == "bob") {
    BobIntegrator bob;
    func( &bob );
  } else if (whichIntegrator == "alice" ) {
    AliceIntegrator alice;
    func( &alice ):
  }
}

как видите, мы вызываем func с другим типом объекта на основе параметра whichIntegrator. Есть забавные способы даже генерировать цепочки if/else if с помощью метапрограммирования, но это, вероятно, не стоит изучать, пока вы не освоитесь с базовым программированием шаблонов.

Functor func должен иметь возможность принимать указатели на любые и все типы, с которыми мы его вызываем. Простым примером может быть func, который просто принимает указатель на базовый класс, а тот, который я привел выше, принимает тип шаблона T*.

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

Сомневаюсь, что здесь так.

person Yakk - Adam Nevraumont    schedule 28.03.2013
comment
Спасибо за ваш ответ, здесь есть что посмотреть, особенно использование ссылки rvalue, которую я еще не использовал. - person Muckle_ewe; 28.03.2013