【问题标题】:Different implementation of a member function from instance to instance in C++在 C++ 中,从实例到实例的成员函数的不同实现
【发布时间】:2018-10-12 08:19:35
【问题描述】:

我知道在 SO 上还有另一个非常相似的 post。但我认为这有点不同。这里的主要问题不是讨论 一些 方法的优点或缺点(尽管它很自然地出现),而是 为了达到多态行为而存在哪些方法以及什么是最好的方法一些标准(见下文)

假设我有一个具有以下结构的类

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

};

我们可以选择不同的实现 Evaluate 函数从一个实例到另一个实例。我想根据以下标准找到最佳(在某种意义上)解决方案。

  • Evaluate 函数的实现(Evaluate 究竟是什么)在实例的构造步骤中确定
  • 添加Evaluate函数的新实现应该尽可能简单
  • Evaluate 函数旨在成为 abstract 类的方法
  • 不仅要使用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 运算符。这实际上是一个问题吗?适当的析构函数会解决它吗?我提供的解决方案还有什么问题?主要问题是:什么是做我想做的事的最佳方式?

【问题讨论】:

  • “我看到那里有危险的 new 运算符。它真的有问题吗” 是的。所以只需使用std::unique_ptr&lt;&gt;std::make_unique() 代替。那么,以后就不用记得写一个对应的同样丑陋的delete了。
  • std::function&lt;double(double)&gt; 是另一种选择。
  • 为什么第二个更有吸引力?在我看来,它似乎对对象着迷。
  • @molbdnilo,此外,我不仅想使用Evaluate 本身,还想使用它的派生词。所以我可以很容易地添加 ReturnPrime 函数并在具体情况下覆盖它,这样函数和它的派生词就在一起了。
  • 您的第二个解决方案是众所周知的“策略”模式。

标签: c++ function class function-pointers


【解决方案1】:

我会简单地将函数传递给Evaluator 类的构造函数中的调用,并将其存储为一个函数指针。不涉及抽象类,但由于 lambda 函数,使用起来很简单

类定义:

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

在优点中,没有使用过动态对象,因此不需要特定的复制或移动构造函数或赋值,也不需要显式析构函数。

【讨论】:

    【解决方案2】:

    第二个是策略模式。我应该选择第二个,更清晰易读。

    我会以这种方式修复 if-branching 和动态构造对象:

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

    这是一个基本的依赖注入,出错更安全。

    当然,如果您愿意,您仍然可以改进您的代码,例如管理智能指针或更好地使用工厂

    Factory 应该是最好的,因为它会负责构建隐藏如何管理的评估类。

    _eval 是一个引用,因为 null 不是一个可接受的值。出于同样的原因,Evaluator 接受一个 const 引用,因为它必须使用有效的 Evaluate 对象构造。

    【讨论】:

    • 使用 Evaluator e( Sin() );,您存储对临时的引用,因此下一行显示 UB。
    猜你喜欢
    • 1970-01-01
    • 2015-06-28
    • 1970-01-01
    • 2013-05-17
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多