Search code examples
c++inheritanceabstract-classvirtualpure-virtual

Implementing operator overloading on abstract interface in C++


I'm writing a class that is an implementation of a "mathematical function".

The "mathematical quality function" can be derived from an abstract class: QualityFunction. It contains a mutable double quality that stores the scalar value of the function once evaluated, and an eval method that is needed to evaluate the function value and has to be implemented by every derived class, because is a pure virtual method.

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

Then every derived class from QualityFunction has to implement the eval methods, because there are many possible QualityFunctions. Two examples are SumSinFunction and SumCosFunction that compute the sum of sin and cos of their argument:

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

This hierarchy is needed because a class Maximizer accepts MathFunction objects and, repeatedly calling eval will find the solution as a vector<double> x that maximizes the overall quality.

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

The problem now is when I want to create linear combinations of MathFunctions by combining derived objects to create a new object that still is a MathFunction instance with their member variables and methods, but that stores a quality that is a linear combinations of the qualities of its components. For example, I would like to implement the operator+ overloading on MathFunction to allow me to create something like this:

SumCosFunction cosfun;
SumSinFunction sinfun;
MathFunction m = cosfun + sinfun;

One first temptative is to overload the operator+ by means of friend function

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

but I can't because the constructor of MathFunction is virtual! So the question is, how can I combine different objects derived from MathFunction to generate an object that can be passed as MathFunction to my Maximizer class? The full code is available on 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;
}

Solution

  • How to implement (an example) pimpl idiom

    MathFunctionImpl will be the base of all functions

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

    We can use UnionFunciton to expand operations between functions:

    class UnionFunction : public MathFunctionImpl
    {
      public:
    
        UnionFunction( MathFunctionImpl* f1, MathFunctionImpl* f2 )
          : f1(f1), f2(f2)
        { }
    
        ~UnionFunction()
        { delete f1; delete f2; }
    
      protected:
    
        MathFunctionImpl* f1;
        MathFunctionImpl* f2;
    };
    

    Now, SumSinFunction and SumCosFunction needs a few changes. I added console messages to test the code

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

    And now a class to Add to functions:

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

    Ok, we need to create a class that stores our pimpl classes:

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

    And that's all. The main to test the code:

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