3

I want to design a general C++ class that defines an operator() function that returns the value of a mathematical function at a point. However, I want that class to be able to be instantiated with either an object that already has the operator() defined or with a table of values that the class will interpolate to give the value. I thought about the following approach:

class MathFunction
{
    MathFunction(Functor& function);
    MathFunction(Mat<double> interpolationValues);

    double operator()(double radius, double angle);
};

where the constructors could set a member variable and double operator() could use a switch statement to determine how to output the double I want. This seems awfully inelegant, though.

A templated version of this class could be of use, but since the two methods don't share any code, would it be wise to do it? And how exactly would the templated version be designed?

Not sure if I'm clear. Please comment if any clarifications are needed.

EDIT: Although I chose Useless's answer for its completeness, I want to underline the speed and clarity of dasblikenlight's contribution. Thanks.

4

5 回答 5

6

This is a prime case for using the strategy pattern: depending on the parameters passed to the constructor, MathFunction should instantiate either a strategy object relying on a Functor, or a strategy object relying on a list of interpolated values. The call of operator () should then forward the call to the strategy, and get the result through an implementation of a common virtual function:

class MathFunction {
    struct Strategy {
        virtual double Calculate(double radius, double angle)=0;
        virtual ~Strategy(){}
    };
    class FunctorStrategy : public Strategy {
        Functor& _function;
    public:
        FunctorStrategy(Functor& function)  : _function(function) {}
        virtual double Calculate(double radius, double angle) {
            return _function(radius, angle);
        }
    }
    class InterpolationStrategy : public Strategy {
        Mat<double> _interpolationValues;
    public:
        InterpolationStrategy (Mat<double> interpolationValues) 
        : _interpolationValues(interpolationValues) {}
        virtual double Calculate(double radius, double angle) {
            return ...; // Use _interpolationValues to do calculation
        }
    };
    unique_ptr<Strategy> _strategy;
public:
    MathFunction(Functor& function)
    : _strategy(new FunctorStrategy(function)) {}
    MathFunction(Mat<double> interpolationValues)
    : _strategy(new InterpolationStrategy(interpolationValues)) {}
    // And now for the complicated part:
    double operator()(double radius, double angle) {
        return _strategy->Calculate(radius, angle); // TA-DA!!!
    }
};
于 2012-12-14T15:49:59.080 回答
1

but since the two methods don't share any code, would it be wise to do it?

If the way to generate the output through operator() is not synergetic through both the mechanisms, I think, it is not such a good idea to put them in one class. I would say that this violates the Single Responsibility Principle a.k.a SRP.

Instead, try to use the Strategy Design Pattern to distribute the functionality into two separate strategy classes. Your class can then be templated on the Strategy

于 2012-12-14T15:49:02.047 回答
1

If MathFunction doesn't have any common implementation, what is it for?

If its purpose is to provide a common interface to two implementations, consider where you'll use it:

  1. in non-templated (or non-dependent) code, where the implementation type could reasonably vary at runtime
  2. in templated code which could reasonably depend on the type of MathFunction, or in code which will always statically know which type it's using

In case 1, using something like the strategy pattern is reasonable. Either MathFunction can be a statically-typed wrapper dispatching to a virtual Strategy, or you can write an abstract MathFunction base class, and instantiate one of two concrete subclasses.

dasblinkenlight already demonstrated the Strategy version, so here is the ABC equivalent (notice how it can use a factory function to hide the implementation details)

// public header
class MathFunction {
public:
    virtual ~MathFunction() {}
    virtual double operator() (double radius, double angle) = 0;
};

MathFunction* make_function(Functor& function);
MathFunction* make_function(Mat<double> interpolationValues);

// implementation
class FunctorMathFunction : public MathFunction {
    Functor& _function;
public:
    FunctorMathFunction(Functor& function) : _function(function) {}
    virtual double operator() (double radius, double angle) {
        return _function(radius, angle);
    }
};
class TableMathFunction : public MathFunction {
    Mat<double> _interpolationValues;
public:
    TableMathFunction (Mat<double> interpolationValues) 
    : _interpolationValues(interpolationValues) {}
    virtual double operator() (double radius, double angle) {
        return ...; // Use _interpolationValues to do calculation
    }
};

MathFunction* make_function(Functor& function) {
    return new FunctorMathFunction(function);
}
MathFunction* (Mat<double> interpolationValues) {
    return new TableMathFunction(interpolationValues);
}

In case 2, templating MathFunction is a good choice: you can either (partially-) specialize it, or use the compile-time equivalent of the Strategy pattern, which is to parameterize MathFunction on a Policy class, to which it delegates the evaluation. If performance is a concern, saving the virtual function call (and enabling in-lining) may be useful.

template <typename EvaluationPolicy>
class MathFunction
{
    EvaluationPolicy eval;
public:
    MathFunction() {}
    MathFunction(EvaluationPolicy const &ep) : eval(ep) {}

    double operator() (double radius, double angle) {
        return eval.calc(radius, angle);
    }
};

class FunctionPolicy
{
    std::function<double(double,double)> func;
public:
    FunctionPolicy(std::function<double(double,double)> f) : func(f) {}
    double calc(double r, double a) { return func(r, a); }
};

class TablePolicy
{
    Mat<double> mat;
public:
    TablePolicy(Mat<double> values) : mat(values) {}
    double calc(double r, double a) { return ....; }
};

The benefit of using a Policy over just writing two distinct concrete classes (FunctionMathFunction and TableMathFunction) comes in showing their relationship, in sharing any code that doesn't depend on the policy, and in the ability to split unrelated concerns into separate policies.

于 2012-12-14T16:02:49.643 回答
0

I'm not sure a templated version of your code is really an improvement of the design. The main issue - as you pointed out - is that you've got two completed different mechanisms at work here that are bunched together into a single class. One way to make it work with templates is that you essentially create two specialised templates with one being the specialisation for the case that takes a function and the other one taking the table. I'm not convinced this is the correct mechanism though.

Unless you can verify by profiling that you absolutely positively cannot stomach the overhead of a virtual function call here, I would go for runtime polymorphism instead of compile time polymorphism and turn your MathFunction class into an interface that provides a pure virtual operator(), then derive the two implementations from it with one handling the function case and the other one handling the table case.

于 2012-12-14T15:51:55.987 回答
0

You could just use polymorphism here:

class MathFunction
{
private:

    class Impl
    {
    public:

       virtual ~Impl() {}
       virtual double compute(double radius, double angle) = 0;
    };

    class FunctorImpl : public Impl
    {
    public:

       explicit FunctorImpl(Functor & function);
       double compute(double radius, double angle);
    };

    class MatImpl : public Impl
    {
       public:

       explicit MatImpl( Mat<double> interpolationValues);
       double compute(double radius, double angle);
    };

    std::unique_ptr< Impl > impl;

public:

    MathFunction(Functor& function) : impl( new FunctorImpl( function ) ) {}
    MathFunction(Mat<double> interpolationValues) : impl( new MatImpl( interpolationValues ) ) {}

    double operator()(double radius, double angle)
    {
       return impl->compute( radius, angle );
    }
};
于 2012-12-14T15:56:40.680 回答