2

I know there is another very similar post here on SO. But I think it's a bit different. The main question here is not a discussion of advantages or disadvantages of some methods (though it appears naturally) but what methods exist in order to reach polymorphic behavior and what is the best one with respect to some criteria (see below).

Suppose I have a class with the following structure

class Evaluator
{
    public :
        double Evaluate( double x )
        {
            /*something here*/
        }

};

where we can choose different implementation of Evaluate function from instance to instance. I want to find optimal (in some sense) solution with respect to the following criteria.

  • Implementation of the Evaluate function (what Evaluate exactly is) is determined at the construction step of an instance
  • It should be as simple as possible to add new implementation of the Evaluate function
  • Evaluate function is intended to be a method of an abstract class
  • Not only Evaluate method is intended to be used but also its derivative. So it would be good if function and its derivative live together

Solutions that I've found could help to understand what I want to reach.

Using pointer to a function

//somewhere in someheader.h
#include <cmath>

namespace evals
{
    inline double Sin( double x ) { return sin(x); }
    inline double Sigmoid( double x ) { return 1. / ( 1. + exp( -x ) ); }
}

//somewhere in Evaluator.h
class Evaluator
{
    protected :
        double (*_eval)( double );

    public :
        Evaluator( double (*eval)( double ) ) : _eval( eval ) { }
        double Evaluate( double x ) { return _eval( x ); }  
};

//somewhere in test.cpp
#include "someheader.h"
#include "Evaluator.h"

Evaluator e( &evals::Sin );
e.Evaluate( 3.14159 );//returns sin( 3.14159 )

Using virtual functions

//somewhere in someheader2.h
#include <cmath>

namespace evals
{
    class AbstractEvaluate
    {
        public :
            virtual double Return( double x ) = 0;
    };

    //concrete evals
    class Sin : public  AbstractEvaluate
    {
        public :
            double Return( double x ) { return sin( x ); }
    };

    class Sigmoid : public AbstractEvaluate
    {
        public :
            double Return( double x ) { return 1. / ( 1. + exp( -x ) ); }
    };
}

//somewhere in Evaluator2.h
#include <string>
#include "someheader2.h"

class Evaluator
{
    protected :
        AbstractEvaluate* _eval;//cannot have an instance, only a pointer

    public :
        Evaluator( std::string evalName )
        {
            //according to some rule return pointer to desired concrete evaluate class e.g.
            if( evalName == "Sin" ) { _eval == new evals::Sin; }
            else if( evalName == "Sigmoid" ) { _eval == new evals::Sigmoid; }
            else { /*some default behavior*/ }
        }

        double Evaluate( double x ) { return _eval->Return( x ); }
}

//somewhere in test.cpp

#include "Evaluator2.h"

Evaluator e( "Sin" );
e.Evaluate( 3.14159 );//returns sin( 3.14159 )

Discussion

While the second approach is more attractive for my personal problem (see the last criterion) I see the dangerous new operator there. Is it actually a problem and would an appropriate destructor resolve it? What else could be a problem with solutions I provided? And the main question: what is the best way to do what I want?.

LRDPRDX
  • 631
  • 1
  • 11
  • 23
  • 3
    "I see the dangerous new operator there. Is it actually a problem" Yes. So just use `std::unique_ptr<>` and `std::make_unique()` instead. Then, you don't have to remember to write a corresponding and equally ugly `delete` later. – underscore_d May 02 '18 at 09:22
  • 3
    `std::function` is an alternative. – Jarod42 May 02 '18 at 09:24
  • 1
    Why is the second more attractive? It looks object-obsessed to me. – molbdnilo May 02 '18 at 09:26
  • @molbdnilo, Also I want to use not only `Evaluate` itself, but its derivative. So I easily could add `ReturnPrime` function and overwrite it in the concrete case so function and its derivative live together. – LRDPRDX May 02 '18 at 09:32
  • 1
    Your second solution is the well-known "Strategy" pattern. – Jean-Baptiste Yunès May 02 '18 at 09:50
  • @LRDPRDX the approach you just wrote is correct indeed. – Moia May 02 '18 at 09:56
  • Possible duplicate of [Virtual Methods or Function Pointers](https://stackoverflow.com/questions/1955074/virtual-methods-or-function-pointers) – doron May 02 '18 at 10:06
  • @LRDPRDX I think that information is an important part of the question and that you should add it there. – molbdnilo May 02 '18 at 10:20
  • @underscore_d, Should I care about destructors if I use `unique_ptr`? I mean should I declare virtual dtor in abstract class and then overwrite it inside derived? If so, what they should be? Default ones is OK? I think that no dtors are needed at all, but not sure. – LRDPRDX May 02 '18 at 12:33
  • @LRDPRDX Good Q. You will need to think about ensuring the right derived destructor of the interface is called. I'd start here: [Deleting derived classes in std::unique_ptr containers](https://stackoverflow.com/questions/24060497/deleting-derived-classes-in-stdunique-ptrbase-containers) It looks like it needs one of (A) giving the interface a virtual dtor, (B) templating `unique_ptr` on the derived deleter, (C) using `shared_ptr` carefully instead, or (D) maybe something else. I don't think it matters even if nothing will leak: `delete`ing a derived from a base pointer is UB, regardless. – underscore_d May 02 '18 at 14:43

2 Answers2

1

The second one is a Strategy Pattern. I should go for the second one, much cleaner and readable.

I would fix the if-branching and the dynamic construction object in this way:

someheader2.h

#include <cmath>

namespace evals
{
    class AbstractEvaluate
    {
        public :
            virtual double Return( double x ) const = 0;
            virtual ~AbstractEvaluate() {} // or = default in C++11
    };

    //concrete evals
    class Sin : public AbstractEvaluate
    {
        public :
            double Return( double x ) const { return sin( x ); }
    };

    class Sigmoid : public AbstractEvaluate
    {
        public :
            double Return( double x ) const { return 1. / ( 1. + exp( -x ) ); }
    };
}

evaluator2.h

#include <string>
class Evaluator
{
    public :
        Evaluator( const evals::AbstractEvaluate& evaluator )
           : _eval(evaluator)
        {
        }

        double Evaluate( double x ) const { return _eval.Return( x ); }

    private:
        const evals::AbstractEvaluate& _eval;

};

#include <iostream>
#include "Evaluator2.h"

int main(int argc, char *argv[])
{
   evals::Sin eval = evals::Sin();
   Evaluator e = Evaluator(eval);
   std::cout << e.Evaluate( 3.14159 );//returns sin( 3.14159 )

   return 0;
}

It's a basic dependency injection and it's safer to error.

Of course you can still improve your code if you want, for instance managing smart pointer or even better using a factory.

Factory should be the best because would take care of the construction of the evaluation class hiding how is managed.

_eval is a reference because null is not an accepted value. For the same reason, Evaluator accept a const reference beacuse it has to be constructed with a valid Evaluate object.

Moia
  • 2,216
  • 1
  • 12
  • 34
1

I would simply pass the function to call in the constructor of the Evaluator class, and store it as a mere pointer to function. No abstract class is involved but thanks to lambda functions, it is simple to use

Class definition:

class Evaluator {
    typedef double (*Func)(double);
    Func func;
public:
    Evaluator(Func func): func(func){}

    double evaluate(double x) {
        return func(x);
    }
};

Usage:

#include <iostream>
#include <cmath>
#include "Evaluator.h"

int main() {
    Evaluator eval1(sin);
    double val = eval1.evaluate(3.1415927);
    printf("%g - %g\n", val, eval1.evaluate(3.1415927 / 2));
    Evaluator evalsig([](double x) { return 1. / ( 1. + exp( -x ) ); });
    printf("%g\n", evalsig.evaluate(0.));
    return 0;
}

Compiles without a warning and outputs as expected:

-4.64102e-08 - 1
0.5

Among the nice points, no dynamic object is ever used, so no need for specific copy or move constructor or assignation nor explicit destructor.

Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252