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