Разная реализация функции-члена от экземпляра к экземпляру в C ++ - PullRequest
0 голосов
/ 02 мая 2018

Я знаю, что есть еще один очень похожий пост здесь, на SO. Но я думаю, что это немного по-другому. Основной вопрос здесь заключается не в обсуждении преимуществ или недостатков некоторых методов (хотя это выглядит естественным образом), а , какие методы существуют для достижения полиморфного поведения и какой является лучшим в отношении некоторых критерии (см. ниже) .

Предположим, у меня есть класс со следующей структурой

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

};

где мы можем выбрать различную реализацию функции Evaluate от экземпляра к экземпляру. Я хочу найти оптимальное (в некотором смысле) решение по следующим критериям.

  • Реализация функции Evaluate (что такое Evaluate) определяется на этапе построения экземпляра
  • Должно быть как можно проще добавить новую реализацию Evaluate функции
  • Evaluate предназначена для использования в качестве метода abstract class
  • Предназначен для использования не только метод Evaluate, но и его производная. Так что было бы хорошо, если бы функция и ее производная жили вместе

Решения, которые я нашел, могут помочь понять, чего я хочу достичь.

Использование указателя на функцию

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

Использование виртуальных функций

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

Обсуждение

Хотя второй подход более привлекателен для моей личной проблемы (см. Последний критерий), я вижу опасного оператора new. Действительно ли это проблема, и может ли ее решить соответствующий деструктор? В чем еще может быть проблема с решениями, которые я предоставил? И главный вопрос: как лучше всего делать то, что я хочу? .

Ответы [ 2 ]

0 голосов
/ 02 мая 2018

Я бы просто передал функцию для вызова в конструктор класса Evaluator и сохранил ее как простой указатель на функцию. Абстрактный класс не задействован, но благодаря лямбда-функциям просто использовать

Определение класса:

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

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

Использование:

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

Компилируется без предупреждения и выдает ожидаемые результаты:

-4.64102e-08 - 1
0.5

Среди приятных моментов динамический объект никогда не используется, поэтому нет необходимости в специальном конструкторе копирования или перемещения или присваивании, ни явном деструкторе.

0 голосов
/ 02 мая 2018

Вторым является шаблон стратегии . Я должен пойти на второй, намного чище и читабельнее.

Я бы исправил if-ветвление и динамический объект построения следующим образом:

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

Это базовое внедрение зависимостей, более безопасное для ошибок.

Конечно, вы все еще можете улучшить свой код, если хотите, например, управлять умным указателем или еще лучше, используя фабрику .

Фабрика должна быть лучшей, потому что она позаботится о построении класса оценки, скрывающего способ управления.

_eval является ссылкой, потому что ноль не является допустимым значением. По той же причине Evaluator принимает константную ссылку, поскольку он должен быть создан с допустимым объектом Evaluate.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...