0

我一直在尝试更多地了解泛型编程,因为我认为我对此了解的不够多。所以我正在考虑如何实现我的一个程序的模板版本。我尝试使用的程序是一个数值积分器程序,用户在其中选择要使用的积分器(即 Euler、Runge Kutta 等),然后对他们选择的任何函数进行积分。我目前这样做的方法是拥有一个名为 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);
    }
}

所以这个方法效果很好,积分器程序运行的很好。现在我知道模板函数是在编译时生成的,并且考虑到我正在使用运行时信息,你将如何使用模板来实现它?如果问题不清楚,我要问的是......在运行时给定用户选择,即使用哪个积分器,我如何使用模板方法调用正确的积分函数?

4

2 回答 2

1

模板不是灵丹妙药,虽然你可以用它们做很多事情,但不要低估你当前使用的多态性的力量。

这可以用模板完成吗?答案是肯定的,使用 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);
}

另一个注意事项是,你应该非常小心这里的内存泄漏,你正在更新和反对,如果你从不释放它,你就会有泄漏。

话虽如此,我希望这个答案表明虽然您可以使用模板,但在这种情况下我不会推荐它,多态在这里工作得很好。这个例子只是在一个非常简单和冗余的情况下展示了模板的实际应用

于 2013-03-28T17:44:25.920 回答
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*模板类型。

只有当运行时多态的开销太高,或者您实际上需要以非统一的方式与不同的集成器类进行交互时,所有这些都是值得做的,而这种方式很难或不可能通过运行时多态来捕获。

我怀疑这里的情况。

于 2013-03-28T18:13:38.537 回答