【问题标题】:Real functions implementation in C++ and operator+ overloadingC++ 中的实函数实现和运算符重载
【发布时间】:2012-05-29 10:06:42
【问题描述】:

我想用 C++ 实现真正的函数。特别是我想评估、区分、添加、乘以这些对象。这是我的实现

class RealFunc {
public:
    virtual int Eval(const double& x, double& result) = 0;
    virtual int Diff(const double& x, double& result) = 0;
};

class Sqrt : public RealFunc {
public:
    int Eval(const double& x, double& result);
    int Diff(const double& x, double& result); 
};

int Sqrt::Eval(const double& x, double& result) {
    if(x<0) return 0;
    else {
        result = sqrt(x);
        return 1;
    }
};

int Sqrt::Diff(const double& x, double& result) {
    if(x<=0) return 0;
    else {
        result = 0.5/sqrt(x);
        return 1;
    }
};

当我尝试添加 RealFunc 对象时,它变得很棘手。我必须创建一个继承自 RealFunc 的 sum 类

RealFunc operator+(const RealFunc& f, const RealFunc& g) {
    Sum_RealFunc h(f,g);
    return h;
};

class Sum_RealFunc : public RealFunc {
public:
    Sum_RealFunc(const RealFunc& f_, const RealFunc& g_) : f(f_), g(g_) {};
    int Eval(const double& x, double& result);
    int Diff(const double& x, double& result);
private:
    RealFunc f;
    RealFunc g;
};

int Sum_RealFunc::Eval(const double& x, double& result) {
    double temp_f,temp_g;
    int success_f,success_g;
    success_f = f.Eval(x,temp_f);
    success_g = g.Eval(x,temp_g);
    result = temp_f+temp_g;
    return success_f*success_g;
};

// Same for Sum_RealFunc::Diff

我的问题是我不能使用f,g 作为Sum_RealFunc 中的成员,因为RealFunc 是抽象的......我应该如何继续获得一个干净的实现?

PS:我放的代码是我正在处理的代码的简单版本(来自 RxR->R 的函数,具有所有微分方向,如果 stepsize 成员不为零则为有限差分以及其他辅助函数)

【问题讨论】:

  • 您的问题是XY Problem 的症状。 为什么你认为你需要一个RealFunc抽象基类? 为什么这个基类double属性?在我看来,这是一个非常混乱的设计,到目前为止提出的解决方案只会让它变得复杂。
  • 你说得对,我的问题是我的设计。到目前为止我得到的答案很复杂,但我最初的问题很简单。我想基本上添加函数,一个函数是一个包含其评估 f(x) 及其精确导数的对象(尽管可以触发使用有限差分进行微分的选项)。
  • 当 diff 返回一个数字时,你将如何评估高阶导数?
  • @MatthieuM。我认为问题在于,在我的实现中,函数 sqrt 不是 RealFunc 类的对象,而是另一个子类。
  • @user877329 在此示例中,Diff 代表“一阶导数”。在我的实际实现中,我使用两个变量的函数,我只对二阶导数感兴趣,所以我得到了DiffXDiffYDiffXX

标签: c++ operator-overloading constants abstract-class


【解决方案1】:

您面临的问题是,您既需要适用于值对象(运算符重载)的功能,也需要仅适用于指针的功能(继承/多态)。

作为一种解决方案,您需要一个带有重载运算符的值对象作为通过指针管理的多态对象的包装器:

class RealFuncImpl {
public:
    virtual ~RealFuncImpl(); // don't forget this for polymorphic objects

    virtual int Eval(const double& x, double& result) = 0;
    virtual int Diff(const double& x, double& result) = 0;
};

class RealFunc {
    std::shared_ptr<RealFuncImpl> impl;
public:

    int Eval(const double& x, double& result);
    int Diff(const double& x, double& result);
};

您将从RealFuncImpl 派生您的Sum_RealFuncImpl 并为RealFunc 实现您的运算符。您可能应该将 Impl 类隐藏在某个“详细”命名空间中,因为您的代码的最终用户不应该看到它们。

编辑:

您的Sum_RealFuncImpl 将包含两个std::shared_ptr&lt;RealFuncImpl&gt; 成员。

【讨论】:

  • 我建议 NOT 使用 shared_ptr,而是使用 unique_ptr(在 C++11 中)或 scoped_ptr 或简单地重新开发 Pimpl 类(并非如此难的)。您确实希望在这里共享所有权。
  • @MatthieuM.:您需要一种克隆函数对象的方法才能实际使用unique_ptr。我想避免这种情况,shared_ptr 是一个相当合理的折衷方案,尽管我同意这不是最佳选择。
  • @leftaroundabout:问题是在OP的设计中,所谓的函数是携带状态的,所以共享不太合理
  • @MatthieuM.:你在说什么州?我没有看到任何状态。 OP 似乎希望能够将函数视为(不可变的)值,并被允许复制它们。因此,我支持shared_ptr
  • @wolfgang:EvalDiff 函数不是 const(和 Sum_RealFunc 有两个属性),我假设 OP 想要有状态函数。事后看来,OP 似乎不太精通 C++。
【解决方案2】:

试试

class Sum_RealFunc : public RealFunc {
    public:
        Sum_RealFunc(RealFunc& f_, RealFunc& g_) : f(f_), g(g_) {};
        int Eval(const double& x, double& result);
        int Diff(const double& x, double& result);
    private:
        RealFunc& f;
        RealFunc& g;
};

现在 f 和 g 是引用,这很好。

【讨论】:

  • 不安全:const 引用 do 将变量的生命周期延长到直接调用函数的框架,但在构造函数完成后它们仍然会离开范围,所以Sum_RealFunc s(somefreefunc(foo), g); s.Eval(bar, baz) 将不起作用。
  • 好的,但是当我实现Sum_RealFunc::Evalf.Eval(x,result) 这样的调用返回以下错误:cannot convert 'this' pointer from 'const RealFunc' to 'RealFunc &amp;'
  • @vanna 这是因为您断言总和不会改变函数的状态。你现在需要说: int Eval(const double& x,double& result) const;差异也一样
  • @leftaroundabout 这是真的。但是只要删除所有的 const:s,我们就迫使 somefreefunc 生存下来。无论如何,将 Eval 和 Diff 声明为 const 成员函数可能还是不错的。
  • 我声明了我的函数 const 并且它起作用了。这正是我需要的,非常感谢。
【解决方案3】:

由于您在构造函数初始化器列表中对其进行了初始化,因此您可以对成员变量进行引用。

【讨论】:

  • 什么意思?例如,如果我将RealFunc f 更改为RealFunc&amp; f,编译器会告诉我error C2440: 'initializing' : cannot convert from 'const RealFunc' to 'RealFunc &amp;'
  • @vanna:那是因为您缺少 const 限定符。
【解决方案4】:

你有两种可能:

  • 按照 wolfgang 的建议进行操作:仅对共享指针使用包装器。这样您就可以创建副本,而无需真正复制派生函数对象。
  • 通过实现clone 成员,使派生类本身可通过基类指针复制。最方便的是从 CRTP 类派生而不是直接从基类派生。我会将其设为本地课程,以免混淆:

    struct RealFunc {
      virtual std::pair<double,bool> operator()       //IMO better than this
                              (double x)const =0;    // reference-argument hackery
      virtual std::pair<double,bool> Diff
                                 (double x)const =0;
    
      virtual RealFunc* clone()const =0;
      template<class Derived>
      struct implementation : RealFunc {
        RealFunc* clone() {
          return new Derived(*static_cast<const Derived*>(this));
        }
      };
    
      virtual ~RealFunc(){}
    };
    

    现在您只需从 implementation 派生您的函数对象,以使其可克隆:

    struct Sqrt : RealFunc::implementation<Sqrt> {
      std::pair<double,bool> operator()(double x) {
        return x>=0
                ? std::make_pair(sqrt(x), true)
                : std::make_pair(0., false);
      }
      ...
    }
    

    您的 sum 函数现在可以使用 std::unique_ptr 很好地完成:

    class Sum_RealFunc : public RealFunc::implementation<Sum_RealFunc> {
      std::vector<std::unique_ptr<RealFunc>> summands;
     public:
      std::pair<double,bool> operator()(double x) {
        double result=0;
        for(auto& f: summands) {
          auto r = (*f)(x);
          if(r.second) result += r.first;
           else return std::make_pair(0., false);
        }
        return std::make_pair(result, true);
      }
    
      Sum_RealFunc(const Sum_RealFunc& cpy) {
        for(auto& f: cpy.summands)
          summands.push_back(f->clone());
      }
    
      //friend operator+=(RealFunc& acc, const RealFunc& add); //doesn't work
    };
    

    不幸的是,这没有足够的间接性来允许编写简单的求和表达式。我在最近的一个项目中做了一些事情,几乎解决了所有这些问题,但有点复杂:我为 每个 实例提供了用任何其他实例覆盖其行为的选项。喜欢

    class RealFunc {
      std::unique_ptr<RealFunc> override;
     public:
      virtual std::pair<double,bool> operator()(double x)const {
        return (*override)(x);
      }
      virtual std::pair<double,bool> Diff(double x)const {
        return override->Diff(x);
      }
    
      auto implemented() -> RealFunc*                              {
        return implement_override? override->implemented() : this; }
      auto implemented()const -> const RealFunc*                   {
        return implement_override? override->implemented() : this; }
    
      virtual RealFunc* clone()const =0;
      template<class Derived>
      struct implementation : RealFunc {
        virtual std::pair<double,bool> operator()(double x)const =0;
        virtual std::pair<double,bool> Diff(double x)const =0;
        RealFunc* clone() {
          return new Derived(*static_cast<const Derived*>(this));
        }
      };
    
      virtual ~RealFunc(){}
    };
    

    这还不是全部,您需要使用这种方法对override 进行大量检查。但最终,它可以让你非常流畅地组合功能,比如

    RealFunc f = const_realfunc(7.);
    for(auto& omega: omegas)
      f += sine_angfreq(omega);
    RealFunc g = f + noise_func(.3);
    ...
    

【讨论】:

  • 关于返回 std::pair&lt;double,bool&gt; 而不是乱用引用的好点。
  • 复杂性给我们带来了什么?与其写RealFunc myFun = f + g;,不如写成... std::unique_ptr&lt;Sum_RealFunc&gt; myFun(new Sum_RealFunc); (*myFun) += *f; (*myFun) += *g;
  • @wolfgang:实际上,double 中有一个NaN 状态,因此不需要布尔值。
  • @MatthieuM。肯定有NaN,但这很麻烦:人们很容易忘记检查它,然后你就会陷入那些无法满足x==x 的丑陋难以追踪的值。如果我必须优化内存和性能,我只会这样做。 — 总是返回非NaN doubles,但在失败的情况下引发异常可能是一个可行的选择。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-11-22
  • 1970-01-01
  • 2012-08-21
  • 1970-01-01
  • 1970-01-01
  • 2017-11-26
  • 2022-07-21
相关资源
最近更新 更多