【问题标题】:Implementing multiplication operator for mathematical functions C++为数学函数 C++ 实现乘法运算符
【发布时间】:2015-02-25 03:54:16
【问题描述】:

我有以下抽象基类:

class Function {
 virtual double Eval(double x) const = 0;
};     

我希望能够在我的主文件中使用 f * g 或 f->operator *(g) 之类的表达式,其中 f 和 g 是 Function 类的具体对象,例如当我想计算时一个定积分,以便我可以写:

AnIntegrationMethod(f*g);

我想出的一个相当简单的方法是声明一个 Product 类(只显示头文件,实现很明显):

class Product: public Function {
 public: Product(Function *g, Function *f); 
  ~Product(); 
  virtual double Eval(double x) const; //return _g->Eval(x)*_f->Eval(x)
 private: Function *_g; Function *_f;
 };

然后在我的任何函数中

#include "Product.h"

class ConcreteFunction: public Function {
 public: 
   ConcreteFunction(); 
  ~ConcreteFunction(); 
  virtual double Eval(double x) const;
 Function* operator*(Function *f) {return new Product(this, f);}
 };

这实际上适用于简单的东西,但问题是运算符 * 仅在基类的单个派生类中定义,而不是为每个可能的派生类定义。这意味着,例如,如果我有一个表示数学函数的具体对象 f,我可以调用 f->operator *g 但如果我想再次调用 operator * 来获取对象 (f->operator * g)- >operator * f 我不能这样做,因为函数 f* g 没有将 * 运算符定义为 f。

我想我应该直接在我的基类中定义运算符 * 但是我不知道如何实现该运算符,因为我真的不知道如何为产品获取正确的 Eval 函数,因为我无法使用现在是 Product 类,在类 Function 本身中使用从类 Function 派生的类 Product 是没有意义的。我想我也很困惑,一般来说写如下内容是否正确:

 Function* Function::operator*(Function *f) {
 Function *g;
 ...
 //operations that allow g to be have the right Eval function
 //which should of the sort this->Eval(x) * f->Eval(x)
 ...
 return g;
 }

感谢任何有关如何进行的提示或建议。我的水平很基础,已经编程两个月了。

【问题讨论】:

  • “return new ...”很可能是内存泄漏。 (或设计噩梦)
  • 您是在乘以函数(数学表达式)还是从函数计算的值?乘法函数(数学表达式)有意义吗?
  • 这种设计不会带来任何好处。如果我理解你想要做什么,你很可能要么想玩矢量,要么为泛型分类创建自己的模板。
  • 我发布了一个基本的模板解决方案,它可以在堆栈上工作,而不是强迫您使用动态分配和自动删除。

标签: c++ operator-overloading


【解决方案1】:

只是一个草图,你可以这样做:

#include <memory>

// Base Function: f(x) = x
class Function
{
    protected:
    struct Implementation
    {
        virtual ~Implementation() {}
        virtual double evaluate(double x) const { return x; }
    };

    public:
    Function()
    :   self(std::make_shared<Implementation>())
    {}

    double operator () (double x) const { return self->evaluate(x); }

    protected:
    Function(std::shared_ptr<Implementation> self)
    :   self(self)
    {}

    private:
    std::shared_ptr<Implementation> self;
};
typedef Function Identity;


// Unary Function: u(-f(x))
class UnaryMinus : public Function
{
    protected:
    struct Implementation : Function::Implementation
    {
        Function f;
        Implementation(Function f)
        :   f(f)
        {};

        virtual double evaluate(double x) const override { return -f(x); }
    };

    public:
    UnaryMinus(Function f)
    :   Function(std::make_shared<Implementation>(f))
    {}
};

// Binary Function: u(f(x) + g(x))
class BinaryAdd : public Function
{
    protected:
    struct Implementation : Function::Implementation
    {
        Function f;
        Function g;
        Implementation(Function f, Function g)
        :   f(f), g(g)
        {};

        virtual double evaluate(double x) const override { return f(x) + g(x); }
    };

    public:
    BinaryAdd(Function f, Function g)
    :   Function(std::make_shared<Implementation>(f, g))
    {}
};

// Binary Function: u(f(x) * g(x))
class BinaryMultiply : public Function
{
    protected:
    struct Implementation : Function::Implementation
    {
        Function f;
        Function g;
        Implementation(Function f, Function g)
        :   f(f), g(g)
        {};

        virtual double evaluate(double x) const override { return f(x) * g(x); }
    };

    public:
    BinaryMultiply(Function f, Function g)
    :   Function(std::make_shared<Implementation>(f, g))
    {}
};

inline UnaryMinus operator - (Function f) { return UnaryMinus(f); }
inline BinaryAdd operator + (Function f, Function g) { return BinaryAdd(f, g); }
inline BinaryMultiply operator * (Function f, Function g) { return BinaryMultiply(f, g); }

#include <iostream>
int main() {
    Identity x;
    Function result = -x * (x + x) + x;
    std::cout << result(2) << '\n';
}

【讨论】:

  • 它必须这么复杂真是太疯狂了,但最终结果看起来相当不错!我以为 Product operator*(const Function &f, const Function &g) { return Product(f, g); } 就足够了,但在操作链中它不能获取中间产品的地址。您可以通过复制传递操作数并在类中隐藏实现和真正的继承来解决该问题。
  • 问题:在您的代码中,为什么将result 设置为子类是有效的?为什么这不会导致某种类型的输入错误?
  • @jschultz410 'class Function' 在内部处理多态性。
  • 我想我基本上了解你的代码是如何工作的。我不明白为什么说 Function f = BinaryAdd(Identity(), Identity()); 是合法的 C++为什么该赋值不会导致类型错误?某种隐式类型转换?
  • @jschultz410 很简单:BinaryAdd 是一个 (!) 函数
【解决方案2】:

您可以将operator* 重载为独立函数。将它放在Product.h/cpp 中,即使它不是Product 的成员,因为它与它紧密相关。

Product.h:

Function* operator*(Function *f, Function *g);

Product.cpp:

Function* operator*(Function *f, Function *g) {
    return new Product(f, g);
}

加减法等也一样

或者,您可以将它们实现为成员函数,但将定义放在Function.cpp 中并在其中包含Product.h 等。

请注意,您的设计存在巨大缺陷。您在堆上创建new 函数对象并传递指针。你需要在代码中的某个地方delete他们,我假设在你的解构器中。但是你还需要take care about copying your objects。通常,手动关心正确删除是一场噩梦,并且有自动的方法来做到这一点(称为“内存管理”)。例如,您可以考虑使用智能指针。看看std::shared_ptr。虽然在每种情况下都不是最有效的,但是当您第一次想要学习语言并且没有太多关于内存管理的细节时,通常使用它是一件好事。要将shared_ptr 应用于您的代码,请将每个Function* 替换为shared_ptr&lt;Function&gt;,并将new Function(...) 替换为make_shared&lt;Function&gt;(...)(与其他类型相同)。

另请注意 * 表示数学函数是模棱两可的:在某些上下文/文献中,f*g 表示 将结果相乘,而在其他情况下则表示 函数卷积.

【讨论】:

  • 那么如何在 C++ 中真正解决这个问题呢?他想从 operator* 返回一个不同的派生类(Product),但是使用 new() 会泄漏内存,我认为你不能正确返回引用,所以你不能返回基类(例如 - Function& )。所以他必须通过版权返回一个产品: Product operator*(const Function &f, const Function &g) { return Product(f, g); }?而且他应该在其他任何地方都使用引用而不是指针,对吧?
  • @jschultz410 只需使用智能指针,而不是 raw 指针。我想 unique_ptr 可以在这里使用,但是如果您不想考虑它并且可以稍微开销,您可以简单地使用 shared_ptr 并且应该没问题...shared_ptr&lt;Function&gt; operator*(shared_ptr&lt;Function&gt; f, shared_ptr&lt;Function&gt; g) { return make_shared&lt;Product&gt;(f, g); }
  • @jschultz410 请注意return new 本身不会泄漏内存。使用完对象后不删除返回的指针是泄漏。但是,如果您返回 delete 语句,您将它放在哪里?如果您决定使用原始指针进行手动内存管理,那么在设计程序时必须考虑这个问题。但既然我们有智能指针,这应该不再是一个有效的选项了。
  • 那么栈上没有办法做到这一点吗?你必须求助于 new() 和某种自动清理机制?我特别考虑中间值,例如: (f * g * h * z).eval(5)?
  • 谢谢,我将运算符实现为 Function.h/cpp 的成员函数。我认为在概念上困扰我的是我必须在基类的实现文件中包含派生类的头文件。下一步,真正使用智能指针,我正在研究它们。
【解决方案3】:

这是一个 C++11 通用编程解决方案,它不依赖于继承的多态性(即 - 虚函数)并且不需要动态分配。

我不是 C++ 专家,这可能会得到显着改进,但它可以工作并让想法得到理解。特别是,下面的代码仅适用于双精度函数。您也可以将操作数和返回类型设为模板类型,这样它通常也适用于不同的类型(例如 - 复杂)。我不知道确定模板运算符范围的正确方法,以便您可以使用速记运算符表示法,而不会意外地在具有 operator()(double x) 的其他类型上调用它们(或使它们模棱两可)。如果有人有任何改进此答案的建议,请加入,我将编辑我的答案。

#include <iostream>

using namespace std;

struct Identity
{
  double operator() (double x) const { return x; }
};

struct Constant
{
  template<typename T1>
  Constant(const T1 &x) : _x(x) {}

  double operator()(double x) const { return _x; }

private:
  double _x;
};


template<typename T1>
struct Negate
{
  Negate(const T1 &f) : _f(f) {}

  double operator() (double x) const { return -_f(x); }

private:
  T1 _f;
};

template<typename T1>
struct Reciprocal
{
  Reciprocal(const T1 &f) : _f(f) {}

  double operator() (double x) const { return 1 / _f(x); }

private:
  T1 _f;
};

template<typename T1, typename T2>
struct Sum
{
  Sum(const T1 &f, const T2 &g) : _f(f), _g(g) {}

  double operator() (double x) const { return _f(x) + _g(x); }

private:
  T1 _f;
  T2 _g;
};

template<typename T1, typename T2>
struct Product
{
  Product(const T1 &f, const T2 &g) : _f(f), _g(g) {}

  double operator() (double x) const { return _f(x) * _g(x); }

private:
  T1 _f;
  T2 _g;
};

template<typename T1> 
Negate<T1> operator-(const T1 &f) 
{ return Negate<T1>(f); }

template<typename T1, typename T2> 
Sum<T1, T2> operator+(const T1 &f, const T2 &g) 
{ return Sum<T1, T2>(f, g); }

template<typename T1, typename T2> 
Sum<T1, Negate<T2> > operator-(const T1 &f, const T2 &g) 
{ return Sum<T1, Negate<T2> >(f, Negate<T2>(g)); }

template<typename T1, typename T2> 
Product<T1, T2> operator*(const T1 &f, const T2 &g) 
{ return Product<T1, T2>(f, g); }

template<typename T1, typename T2> 
Product<T1, Reciprocal<T2> > operator/(const T1 &f, const T2 &g) 
{ return Product<T1, Reciprocal<T2> >(f, Reciprocal<T2>(g)); }


int main()
{
  auto f = (Identity() * Constant(4.0) + Constant(5)) / Identity();  // f(x) = (x * 4 + 5) / x; f(2) = 6.5
  auto g = f * f;                                                    // g(x) = f(x) * f(x);     g(2) = 42.25

  cout << f(2) << " " << g(2) << " " << (g / f)(2) << endl;  // prints 6.5 42.25 6.5

  return 0;
}

编辑:这种方法的主要缺点是“公式”的类型必须在编译时完全知道并嵌入到模板生成的类中。这意味着非常复杂的公式将生成许多不同的类和代码。因此,这种方法可能会导致令人讨厌的代码膨胀。此外,您不能执行以下操作:

for (i = 1; i < j; ++i)  // raise f to the jth power (other than 0)
  f *= f;  

因为 f 的类型必须在编译时完全知道,并且乘法迭代地调用新类型。使用类层次结构、动态分配(使用自动清理)和多态性的其他方法可以做到这一点,并且没有代码膨胀的问题。不过,尝试一下还是很有趣的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-05
    • 1970-01-01
    • 2015-05-30
    相关资源
    最近更新 更多