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:
- in non-templated (or non-dependent) code, where the implementation type could reasonably vary at runtime
- 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.