在C++中实现抽象接口的运算符重载

3
我在编写一个实现"数学函数"的类。
这个"数学质量函数"可以从一个抽象类QualityFunction派生出来。它包含一个mutable double quality,一旦计算出函数值就会被存储为标量值,还包括一个eval方法。这个方法需要每个派生类自己实现,因为它是一个纯虚方法。
#include <vector>
#include <iostream>
#include <cmath>

using std::cout;
using std::cerr;
using std::endl;
using std::sin;
using std::cos;
using std::vector;

class MathFunction
{
protected:
    mutable double quality; // mutable keyword allows to modify quality even in const methods.
    virtual void eval(const vector<double> &x) const = 0;

public:
    virtual ~MathFunction() {}

    double &operator()()
    {
        return quality;
    }

    double &operator()(const vector<double> &x) const
    {
        eval(x);
        return quality;
    }
};

那么从QualityFunction派生的每个子类都必须实现eval方法,因为有许多可能的QualityFunction。两个例子是SumSinFunction和SumCosFunction,它们计算其参数的sincos之和:

class SumSinFunction : public MathFunction
{
public:
    SumSinFunction(){};
    ~SumSinFunction() {};

protected:
    void eval(const vector<double> &x) const
    {
        quality = 0;
        for (size_t i=0; i<x.size(); ++i)
            quality += sin(x[i]);
    }
};

class SumCosFunction : public MathFunction
{
public:
    SumCosFunction(){};
    ~SumCosFunction() {};

protected:
    void eval(const vector<double> &x) const
    {
        quality = 0;
        for (size_t i=0; i<x.size(); ++i)
            quality += cos(x[i]);
    }
};

这种层次结构的设计是为了使类Maximizer能够接受MathFunction对象,并通过重复调用eval方法来寻找一个解决方案,即使得一个vector<double> x最大化整体质量。

class Maximizer
{
public:
    Maximizer(){}
    vector<double>  maximize(const MathFunction &f)
    {
        // do some operations to maximize it
        // and return the maximized value
        return std::vector<double>();
    }
};

现在的问题是,当我想要通过组合派生对象来创建线性组合的MathFunctions时,我需要创建一个新对象,该对象仍然是一个MathFunction实例,并具有它们的成员变量和方法,但存储的质量是其组件质量的线性组合。例如,我想在MathFunction上实现operator+重载,以允许我创建类似以下内容的东西:
SumCosFunction cosfun;
SumSinFunction sinfun;
MathFunction m = cosfun + sinfun;

首先尝试使用友元函数重载operator+

friend MathFunction& operator+(const MathFunction &f1, const MathFunction &f2)
{
    MathFunction *f;
    // do something
}

但是我不能这样做,因为MathFunction的构造函数是虚拟的!所以问题是,如何组合派生自MathFunction的不同对象,生成一个可以作为MathFunction传递给我的Maximizer类的对象? 完整代码可在coliru上找到http://coliru.stacked-crooked.com/a/3c33664066a3658b
int main()
{
    vector<double> x;
    for (int i=0; i<10;i++)
        x.push_back(i);

    SumCosFunction cosfun;
    SumSinFunction sinfun;

    //MathFunction F;// = cosfun+sinfun;

    Maximizer opt;
    opt.maximize(cosfun);

    return 0;
}

你不能从 MathFunction 派生一个类(例如 ContainerFunction)吗? 这个 ContainerFunction 可以存储一个 MathFunction 对象列表(例如 std::vector),当调用 eval 时,这些对象都会被执行。 - Simon Kraemer
你可以使用pimpl习惯用法。MathFunction.pimpl -> MathFunctionImpl,并且你的数学类继承自MathFunctionImpl。这样就不会有运算符重载的问题了。 - eferion
你能详细解释一下你打算如何使用pimpl惯用法吗? - linello
我猜你是指“数量”而不是“质量”? - Lingxi
是的,那只是一个变量,用于存储函数一旦被评估后的值。这里的代码已经为了清晰起见从另一个代码中进行了调整。 - linello
3个回答

1
如何实现(一个例子)pimpl习惯用法 MathFunctionImpl将是所有函数的基础。
class MathFunctionImpl
{
  protected:
    mutable double quality; // mutable keyword allows to modify quality even in const methods.

    virtual void eval(const vector<double> &x) const = 0;
  public:

    virtual ~MathFunctionImpl() {}

    double &operator()()
    { return quality; }

    double &operator()(const vector<double> &x) const
    {
        eval(x);
        return quality;
    }

    virtual MathFunctionImpl* Clone() const = 0;
};

我们可以使用UnionFunciton扩展函数之间的操作:
class UnionFunction : public MathFunctionImpl
{
  public:

    UnionFunction( MathFunctionImpl* f1, MathFunctionImpl* f2 )
      : f1(f1), f2(f2)
    { }

    ~UnionFunction()
    { delete f1; delete f2; }

  protected:

    MathFunctionImpl* f1;
    MathFunctionImpl* f2;
};

现在,SumSinFunctionSumCosFunction需要进行一些更改。我添加了控制台消息以测试代码。
class SumSinFunction : public MathFunctionImpl
{
  public:
    SumSinFunction(){}
    ~SumSinFunction() {}

  protected:
    void eval(const vector<double> &x) const
    {
      quality = 0;
      for (size_t i=0; i<x.size(); ++i)
      {
        if( i>0) std::cout << "+";
        std::cout << "sin(" << x[i] << ")";
        quality += sin(x[i]);
      }
    }

    MathFunctionImpl* Clone() const
    { return new SumSinFunction; }
};

class SumCosFunction : public MathFunctionImpl
{
  public:
    SumCosFunction(){}
    ~SumCosFunction(){}

  protected:
    void eval(const vector<double> &x) const
    {
      quality = 0;
      for (size_t i=0; i<x.size(); ++i)
      {
        if( i>0) std::cout << "+";
        std::cout << "cos(" << x[i] << ")";
        quality += cos(x[i]);
      }
    }

    MathFunctionImpl* Clone() const
    { return new SumCosFunction; }
};

现在有一个用于添加函数的类:

class SumFunctions : public UnionFunction
{
  public:

    SumFunctions(MathFunctionImpl* f1, MathFunctionImpl* f2 )
      : UnionFunction(f1,f2)
    { }

    ~SumFunctions()
    { }

    void eval(const vector<double> &x) const
    {
      std::cout << "(";
      quality = (*f1)(x);
      std::cout << "+";
      quality += (*f2)(x);
      std::cout << ")";
    }

    MathFunctionImpl* Clone() const
    { return new SumFunctions(f1->Clone(),f2->Clone()); }
};

好的,我们需要创建一个类来存储我们的pimpl类:

class MathFunction
{
  public:

    MathFunction( MathFunctionImpl* impl )
      : impl(impl)
    { }

    ~MathFunction()
    { delete impl; }

    double &operator()()
    {
      return (*impl)();
    }

    double &operator()(const vector<double> &x) const
    {
      return (*impl)(x);
    }

    // This method can be friend
    MathFunction operator+(const MathFunction& f2) const
    {
      return MathFunction(new SumFunctions(impl->Clone(), f2.impl->Clone()));
    }

  private:
    MathFunctionImpl* impl;
};

就这些。测试代码的主要部分:

int main()
{
  vector<double> x;
  for (int i=0; i<10;i++)
    x.push_back(i);

  MathFunction f1( new SumCosFunction );
  MathFunction f2( new SumSinFunction );
  MathFunction sum = f1 + f2;

  double value = sum(x);
  std::cout << "=" << value << std::endl;
  return 0;
}

很复杂,但绝对是我在寻找的!谢谢!这样一来,“Maximizer”只需接受“MathFunction”的一个实例,所有管理工作都在后台完成! - linello
@linello 你可以实现一个工厂来简化新函数的创建,或考虑使用智能指针来减少内存消耗。 - eferion

0
你需要返回一个MathFunction,其eval是由另外两个MathFunctioneval计算而来。你没有一个可以做到这一点的类,所以你需要定义一个。
class SumFunction : public MathFunction
{
   MathFunction *one, *two;
  public:
   SumFunction (MathFunction* one, MathFunction* two) 
     : one(one), two(two) {}
   void eval(const vector<double> &x) const
   {
     one->eval();
     two->eval();
     quality = (*one)() + (*two)(); // is this ever correct?
   }
};

顺便提一下,我认为你的设计会受益于使eval返回一个计算出的质量值并丢弃operator()和可能的quality。此外,在实际代码中,您可能希望使用std::shared_ptr而不是原始指针。


1
通过使 eval 返回计算值,我失去了效率,因为有时求解器只需获取已计算的值而无需进行评估。 - linello
按照你的方式做会失去正确性,因为你不知道 eval 在你的函数中是否被调用,并且参数不同。 - n. m.

0

我会选择基于std::function的不同设计。

typedef std::function<double (std::vector<double> const&)> MathFunction;

MathFunction sum(MathFunction f1, MathFunction f2)
{
     return [f1,f2](std::vector<double> const& x) {
          return f1(x) + f2(x); 
     };
}

您可以使用模板方法或策略设计模式来简化单个函数的定义,或编写一个使用std :: function<double(double)>的通用函数,或者使用模板参数。我将坚持您最初的示例。

class SumCosFunction
{
public:

    double operator()(const vector<double> &x) const
    {
        if (!quality) {
            *quality = 0;
            for (size_t i=0; i<x.size(); ++i)
                *quality += cos(x[i]);
        }
        return *quality;
    }

private:
    mutable std::experimental::optional<double> quality;
};

class SumSinFunction
{
public:

    double operator()(const vector<double> &x) const
    {
        if (!quality) {
            *quality = 0;
            for (size_t i=0; i<x.size(); ++i)
                *quality += sin(x[i]);
        }
        return *quality;
    }

private:
    mutable std::experimental::optional<double> quality;
};


Combining both functions is simple:

auto consSin = sum( SumCosFunction{}, SumSinFunction{} );

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接