2015-09-10 2 views
3

Я пишу класс, который представляет собой реализацию «математической функции».Реализация перегрузки оператора на абстрактном интерфейсе в C++

«Математическая функция качества» может быть получена из абстрактного класса: 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 s. Два примера SumSinFunction и SumCosFunction, что вычислить сумму sin и cos их аргумент:

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 экземпляром с их переменными и методами, но сохраняет качество, которое является линейные комбинации качеств его компонентов. Например, я хотел бы реализовать operator+ перегружать на MathFunction, чтобы позволить мне создать что-то вроде этого:

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

Один первый temptative является перегружать 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; 
} 
+0

Не можете ли вы получить класс (например, 'ContainerFunction') из' MathFunction'? Эта 'ContainerFunction' может хранить список (например,' std :: vector') объектов 'MathFunction', которые выполняются при вызове' eval'. –

+0

вы можете использовать идиому pimpl. 'MathFunction.pimpl -> MathFunctionImpl' и ваши математические классы наследуются от' MathFunctionImpl'. Тогда у вас не будет проблем с перегрузкой оператора. – eferion

+0

Можете ли вы немного рассказать о том, как вы хотите использовать идиому pimpl? – linello

ответ

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

сейчас , SumSinFunction и SumCosFunction нуждается в нескольких изменениях. Я добавил сообщения консоли для проверки кода

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

Это сложно, но определенно то, что я искал! Благодаря! Таким образом, «Максимизатор» может принимать только значение «MathFunction», и вся администрация выполняется в фоновом режиме! – linello

+0

@linello вы можете реализовать завод, чтобы упростить создание новых функций или использовать интеллектуальные указатели для уменьшения потребления памяти – eferion

0

Вам нужно возвращать MathFunction чей eval вычисляется в терминах eval двух других MathFunction с. У вас нет класса, который делает это, поэтому вам нужно его определить.

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

+0

Делая это по-своему, вы теряете правильность, потому что не знаете, вызвал ли eval вашу функцию из других источников другим аргументом. –

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{}); 
Смежные вопросы