2

Is it possible to pass an abstract class as an argument and use its member functions as shown below? (Summary: A model is created that needs a solver derived from a base class, and where the system of equations to be solved may change).

#include <iostream>
#include <string>
#include <functional>

class AbstractSolver {
  public:
  virtual ~AbstractSolver(){}
  virtual double solve() = 0;
  virtual void setSystem(std::function<double(double,double)> system) = 0;
};

class ConcreteSolver : public AbstractSolver {
  protected:
    double stepSize;
    double initialValue;
    std::function<double(double, double)> system;
  public:
  ConcreteSolver(double stepSize,
                 double initialValue) :
     stepSize(stepSize),
     initialValue(initialValue) {}

  double solve() {
    // implementation here ...
  }

  void setSystem(std::function<double(double,double)> system) {
    this->system = system;
  }
};

class Model {
  protected:
    std::function<double(double,double,double)> system;
    AbstractSolver * solver;
  public:
    Model(AbstractSolver * solver,
          std::function<double(double, double, double)> system ) {
      this->solver = solver;
      this->system = system;
    }

    ~Model() {
       delete solver;
    }

    double getSolution(double tau) {
      std::function<double(double, double)> sys =
          [this, tau](double t, double y) { return system(t, y, tau); };
      solver->setSystem(sys); // SIGSEGV
      return solver->solve();
    }
};

int main(){
  std::function<double(double, double, double)> system = 
    [](double t, double y, double tau) {
         return 0;// return some mathematical expression
      };
  AbstractSolver * solver = new ConcreteSolver(0, 1);
  Model model = Model(solver, system);
  model.getSolution(0.1);

}

This will compile, but the problem is that it seg faults where I've put the comment above. Can anyone explain why (I wasn't able to find anything with regards to this)? Your suggestions are welcome

A.I.
  • 89
  • 1
  • 9
  • 2
    Perfect opportunity to [learn how to use a debugger](http://ericlippert.com/2014/03/05/how-to-debug-small-programs/). – Captain Obvlious Jan 14 '16 at 20:35
  • 1
    You need to follow the [Rule of Three](http://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). Since you lack a copy constructor/copy assignment, it's deleting your solver after your temporary Model is created, copied, and destroyed. – Dave S Jan 14 '16 at 20:37
  • Make your derived class methods virtual too. – George Houpis Jan 14 '16 at 20:50
  • by the way, in which language is the code above? public class AbstractSolver, public Model ... even without class. It does not even compile, how could we say what is wrong... – ead Jan 14 '16 at 20:53
  • @Captain Obvlious I'm already using GDB. It doesn't really tell me a lot. that's why I put that here. – A.I. Jan 14 '16 at 21:03
  • 1
    @ead It's coded in C++. I've put here only the relevant part, the actual code is much larger and complex so I've tried to capture the essence. I realize I had a couple of mistakes there... I've updated the above: sorry I'm a bit tired. – A.I. Jan 14 '16 at 21:03
  • @DaveS you mean a copy constructor for ConcreteSolver? – A.I. Jan 14 '16 at 21:09
  • @AlvinI No, on model. `Model model = Model(solver, system);` creates a temporary Model, then copy constructs it onto Model, and deletes the temporary. That calls the destructor, which deletes your Solver – Dave S Jan 14 '16 at 21:13
  • Please compile your code before posting it here: class AbstractSolver has destructor ~Solver - it's never going to work. There are much more errors... – ead Jan 14 '16 at 21:13
  • 1
    This code shows the most terrible pattern of all. You are accepting a pointer in the constructor, and than you delete this in the destructor. Who gave you guarantees this pointer is deletable? Remove all your terrible pointers, replace them with `unique_ptr`s, and those problems will go away. – SergeyA Jan 14 '16 at 21:17
  • @ead code compiles now – A.I. Jan 14 '16 at 21:25
  • 1
    You cannot pass an abstract class. You can pass an object – Ed Heal Jan 14 '16 at 21:29
  • 2
    @EdHeal that's why it's a pointer – A.I. Jan 14 '16 at 21:33
  • @AlvinI - It is a pointer to an object - not a class. – Ed Heal Jan 14 '16 at 21:34
  • @EdHeal thanks for the comment – A.I. Jan 14 '16 at 21:40
  • Personally I would dump the code and have a rethink - passing function pointers around is not a good idea as other ways are better – Ed Heal Jan 14 '16 at 21:43
  • 1
    @AlvinI And does the code you posted crash? At least it does not on my machine, even though it has nothing to say... – ead Jan 14 '16 at 21:44
  • @AlvinI It may be not the best piece of software engineering seen by mankind, but there is nothing to my eye that could be responsible for a SIGSEGV. I would bet my money that the problem is somewhere else... – ead Jan 14 '16 at 22:01
  • @ead OK... well I'm not the best piece of software engineer. How would I code this in such a way that it would be better in terms of software engineering? (I know i'm diverging from the initial question, but I would like to know anyway). – A.I. Jan 14 '16 at 22:25
  • @AlvinI sorry it was not meant as an offence... see my answer for suggestions (comming soon) – ead Jan 14 '16 at 22:32
  • I guess I would lose my bet after all – ead Jan 14 '16 at 23:00

1 Answers1

5

To your first question: you can have abstract classes as parameter of methods or constructor - this is the core of the polymorphism. After have said this, off to your problem.

The problem in your code is a double delete/dangling pointer. And as pointed out by others you should adhere to Rule of Three and (better) use smart pointers rather than raw pointers to prevent this.

The problem starts at line:

Model model = Model(solver, system);

With Model(solver, system) a Model-object is created and a copy of it is assigned to model. Now there are two Model-object, but both share the same solver-pointer (because you didn't overwritten the assingment operator, remember the Rule of Three!). When the fist of them goes out of scope, the destructor is called and the pointer solver is destroyed. The second Model-object has now a bad pointer. When the second object goes out of scope, the solver will be freed once again - which ends in undefined behaviour. My compiler is forgiving and I don't see anything.

It is worse, if you dereference the solver-pointer after it was freed once - a segfault is the result. This is not the case in your code, because both objects go out of scope directly after each other at the end of main. Yet you can trigger the segfault by:

  Model model = Model(NULL, system);
  {
     model = Model(solver, system);
  }
  model.getSolution(0.1);

Now, the first model goes out of scope before the call of getSolution because its scope is in between {}. Thereafter the model has a bad pointer and dereferences it somewhere along the call of getSolution.

A better design would be to use smart pointers rather then raw pointer: std::shared_ptr<AbstractSolver> or (depending on your design) std::unique_ptr<AbstractSolver>

With shared pointer your Model class would look like:

class Model {
  protected:
    std::function<double(double,double,double)> system;
    std::shared_ptr<AbstractSolver> solver;
  public:
    Model(const std::shared_ptr<AbstractSolver> &solver,
          std::function<double(double, double, double)> system ) {
      this->solver = solver;
      this->system = system;
    }

   //not needed anymore
   // ~Model() {//nothing to do}

... };

The biggest gain is: you don't have to manage the memory at all and thus cannot make mistakes. Solver pointer is deleted first after the last owner goes out of scope. Due to the Rule of Three you don't need neither copy constructor nor assignment operator, as you don't need destructor.

If you come from java: shared_ptr behaves (almost) exactly as pointers from java.

If your Model owns (opposed to shares) the solver, you should use std::unique_ptr-this way it is much clearer and you can be sure, that your solver isn't shared.

With this you error should go away. But there are some other points you could improve:

a) use rather private than protected members: with protected members the subclasses are tighter coupled with the base class as it would be the case with private members - it is much harder later on to change those.

b) don't use:

Model model = Model(solver, system);

use rather:

Model model(solver, system);

this way you initialize the object directly and don't need any copying, which is faster.

ead
  • 32,758
  • 6
  • 90
  • 153