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

Я пытался узнать больше о родовом программировании, так как я думаю, что недостаточно о нем знаю. Поэтому я думаю о том, как реализовать шаблонную версию одной из моих программ.
Программа, с которой я пытаюсь это сделать, представляет собой программу числового интегратора, в которой пользователь выбирает, какой интегратор использовать (например, Эйлер, Рунге Кутта и т. Д.), А затем интегрирует любую функцию по своему выбору.
Мой текущий способ сделать это — иметь абстрактный базовый класс Integrator и несколько производных классов, которые реализуют метод интеграции.
Таким образом, код будет выглядеть примерно так (гораздо больше, но это просто для демонстрации методологии). Обратите внимание, что для этого я использую Qt и объявляю интегратор * интегратор; в классе 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);
}
}

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

0

Решение

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

Можно ли это сделать с помощью шаблонов? Ответ — да, и это выглядит так, используя 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);
}

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

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

1

Другие решения

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

Я бы взял ваш 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* тип шаблона.

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

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

1

По вопросам рекламы [email protected]