質問

I've been trying to learn more about generic programming since it's something I think I don't know enough about. So I'm thinking about how I would implement a template version of one of my programs. The program I am trying to do this with is a numerical integrator program in which the user selects which integrator to use (i.e. Euler, Runge Kutta, etc) and then integrates whatever function they choose. My current method of doing this is by having an abstract base class called Integrator, and several derived classes that implement the integration method. So the code would look something like this (much more going on but this is just to show the methodology). Note that I use Qt for this and I declare an Integrator *integrator; in the MainWindow class.

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();
}

with getIntegrator essentially using the factory method

// 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);
    }
}

So this method works fine, and the integrator program runs very well. Now I know that template functions are generated at compile time and given that I'm using run time information, how would you implement this using templates? If the question is not clear, what I'm asking is... Given a user choice at run time, that being which integrator to use, how do I call the correct integration function using a template method?

役に立ちましたか?

解決

Templates are not a silver bullet, while you can do a lot with them, don't discount the power of polymorphism which you are currently using.

Can this be done with templates? The answer is Yes and it looks like this using C++11 and shared_ptr:

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

And in your caller:

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

One other note, is you should be very careful about Memory Leaks here, you are newing up and object and if you're never releasing it, you will have a leak.

All that being said, I hope this answer shows that while you can use templates, I wouldn't recommend it in this case, Polymorphism works well here. This example just shows templates in action, in an extremely simple and redundant case

他のヒント

Suppose I wanted to write this entire system with staticly typed integrators.

I would take your on_integrateButton_clicked, and change it to something like this:

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) );
  } );
}

As you can see, the code seems a bit backwards.

We defer the type selection as long as possible, and at that point we have it call a functor with a pointer to the integrator. This means that the code that uses the integrator has full type information of the integrator, and isn't dealing with it abstractly.

Usually, runtime polymorphism or type erasure is sufficient for these kinds of problems, however.

Now, RunWithChosenIntegrator is a bit of a strange beast. It would have a signature lookling like this:

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 ):
  }
}

as you can see, we call func with a different type of object based on the whichIntegrator parameter. There are fun ways you can even generate the if/else if chains using metaprogramming, but that probably isn't worth learning until you are more comphy with basic template programming.

The Functor func needs to be able to accept pointers to any and all of the types we call it with. A simple example might be a func that just takes a pointer to a base class, and the one I gave above takes a T* template type.

All of this is only worth doing if either the overhead of runtime polymorphism is too high, or if you actually need to interact with the different integrator classes in non-uniform ways that is difficult or impossible to capture via runtime polymorphism.

I doubt that this is the case here.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top