3

If I integrate a system with boosts odeint module, using a class to define the derivative, the destructor of this class is called very often.

  1. Is this behavior intended?
  2. Why is it the case?
  3. What should I do if I want to allocate arrays dynamically in this class?

For example, this code:

#include <iostream>
#include <boost/numeric/odeint.hpp>

using namespace std;
using namespace boost::numeric::odeint;

class foo
{
public:
    virtual ~foo() {
        std::cout << "destructor called" << std::endl;
    }

    void operator()(const double &x, double &dxdt, double t) const    {
        dxdt = 1;
    }
};

int main( int argc , char **argv )
{
    double x = 0;
    const double dt = 0.1;
    typedef runge_kutta4< double > stepper_type;

    integrate_const( stepper_type() , foo(), x , 0.0 , 10.0 , dt);

    return 0;
}

calls the destructor around 400 times. (I'am a beginner in c++)

TheIdealis
  • 707
  • 5
  • 13
  • @Someprogrammerdude No, `integrate_const` is a template function (in boost 1.71.0). – Benjamin Bihler Oct 24 '19 at 11:29
  • 1
    A look into the documentation shows that `integrate_const` is a function with a template parameter `System [=foo]`. The argument is copied multiple times – actually 4 times in each iteration step since it's the classical Runge-Kutta method for 100 iterations. If you have to store some extra data in `foo`, I'd advice to build a thin wrapper around that data with pointer semantics. – Albjenow Oct 24 '19 at 11:30
  • @Albjenow That would be a good answer. – Some programmer dude Oct 24 '19 at 11:32
  • Your question is great!!! If have the same effect here, but I hadn't realized that until your question. – Benjamin Bihler Oct 24 '19 at 11:48

2 Answers2

1

The destructor is only called once at the end of the program if

  1. there is an instantiation of foo in main and
  2. if std::ref() is used in the call to integrate_const()

Like this:

#include <iostream>
#include <boost/numeric/odeint.hpp>
#include <functional>

using namespace boost::numeric::odeint;

class foo
{
public:
    virtual ~foo() {
        std::cout << "destructor called" << std::endl;
    }

    void operator()(const double &x, double &dxdt, double t) const   {
        dxdt = 1;
    }
};

int main( int argc , char **argv )
{
    double x = 0;
    const double dt = 0.1;
    typedef runge_kutta4< double > stepper_type;
    foo myfoo;
    integrate_const( stepper_type() , std::ref( myfoo ), x , 0.0 , 10.0 , dt);
    return 0;
}

You can allocate any kind of data dynamically in the foo class, for instance by adding a simple setter function .setData() that could be called from main with

myfoo.setData(myArray);

prior to the call to integrate_const().

RHertel
  • 23,412
  • 5
  • 38
  • 64
1

Simple: just trace back the calls in a debugger to your destructor.

You will see that the first level is:

template<class Stepper, class System, class State, class Time> size_t integrate_const( Stepper stepper, System system, State &start_state, Time start_time, Time end_time, Time dt)

which after several intermediary steps has a loop in it:

while( less_eq_with_sign( static_cast<Time>(time+dt) , end_time , dt ) ) { obs( start_state , time ); st.do_step( system , start_state , time , dt ); ...

found in usr/include/boost/numeric/odeint/integrate/detail/integrate_const.hpp:59

and unfortunately all the parameters are being sent down via value, not reference in the boost code. So it will create and destroy a lot of temporary objects based from the one you have created.

If you want to allocate arrays dynamically I would recommend to use std::vector because approaching this issue via C style arrays will take a lot of time to debug.

Ferenc Deak
  • 34,348
  • 17
  • 99
  • 167
  • Thanks a lot for your answer. I marked RHertels as the correct one since it resolves my main issue directly, but yours was really helpful too. Switching to std::vector is a good point. I looked in the code before, but I cannot trust my understanding of it yet. – TheIdealis Oct 24 '19 at 11:54