views:

155

answers:

3

Hi, I want to know if there is a design pattern for specifying options to a set of algorithms. I am using C++. Let me describe my problem. I am having a set of algorithms and these algorithms have different options. I want to design a single point access to these algorithms. Something similar to a strategy pattern. This single point access is a controller class which takes input as a generic options class. Depending upon the options, a suitable algorithm will be used. I want to generalize these options so that i can extend algorithms and the client. Thanks, Amol

+7  A: 

An often-used pattern is to create a policy class which is passed as a template type to your class and then inherited from privately:

template <typename Policy>
class fancy_algorithm : private Policy {
};

One famous example of this is the std::allocator class which is often inherited in that manner:

template <typename T, typename Alloc = std::allocator<T> >
class my_container : private Alloc {
public:
    typedef Alloc allocator;

    // It's necessary to make the base class names available explicitly.
    typedef typename allocator::pointer pointer;

    using allocator::construct;
    // …
};
Konrad Rudolph
Interesting! At first I thought: Why derive from it? It's not an is-a relationship. But on second thought it makes sense: We inherit all the getters/setters for the options, which is very convenient, so we can set them from the outside.
@dehmann: Yes, it's counter-intuitive. However, private inheritance doens't model “is-a” relationships, but rather “is-implemented-in-terms-of”. Private inheritance in itself is a highly confusing concept because it is not related to public inheritance *at all*. Basically, private inheritance is …
Konrad Rudolph
(cont’d): … just a convenience notation for private membership in a class (composition). Don’t use it, except in strange cases like the above. This is a special case because it has become an idiom of the language and many C++ library authors immediately know what it means in this context.
Konrad Rudolph
Also, if your algorithms require parameters at construction time, you can handle this by requiring that any Policy class has a nested type called Params, and then provide a constructor inside fancy_algorithm that takes an argument of this type and passes it to the contained Policy object.
j_random_hacker
j_random_hacker: excellent remark, completely forgot about those. How about making an own answer out of this technique? A comment isn't really a good place for it. You'll get my up-vote. ;-)
Konrad Rudolph
Thanks Konrad, I've added a solution with an example.
j_random_hacker
+3  A: 

I agree with Konrad on policy-based design. I also recommend Modern C++ Design: Generic Programming and Design Patterns Applied. After this book your vision of C++ will be changed forever ;)

Anonymous
+2  A: 

Building on Konrad's suggestion of using policy types, if your algorithms require parameters at construction time, you can handle this cleanly by requiring that any Policy class has a nested type called Params, and then provide a constructor inside fancy_algorithm<Policy> that takes an argument of this type and passes it to the contained Policy object:

template <typename Policy>
class fancy_algorithm : private Policy {
public:
    typedef typename Policy::Params Params;    // Need to redeclare :(

    explicit fancy_algorithm(Params params = Params()) : Policy(params) {}
};

Any relevant parameters need to be packaged into a single object of type Policy::Params.

The Policy class is always constructed with a single argument of type Policy::Params. To work with policy classes that (may) require no parameters, provide a default constructor (or use the implicitly declared one) in Params, not in Policy. This way, by using a default value for the fancy_algorithm<Policy> constructor as above, we enable convenient default-construction of a fancy_algorithm<Policy> whenever Policy::Params has a default constructor (i.e. when the Policy doesn't require any parameters). No safety is lost: if Policy::Params lacks a default constructor (indicating that some parameters are required), any attempt to default-construct a fancy_algorithm<Policy> object will fail at compile time.

Example:

struct multiply_by_params {
    multiply_by_params(int x /* = 42 */) : _x(x) {}     // See bottom
    int get() const { return _x; }    // Or, just make multiply_by a friend

private:
    int _x;
};

struct multiply_by {
    typedef multiply_by_params Params;
    multiply_by(Params p) : _x(p.get()) { /* Other initialisation */ }

    // Other code implementing the strategy (e.g. an operator()())
    ...

private:
    int _x;
};

fancy_algorithm<multiply_by> a(69); // Always compiles
fancy_algorithm<multiply_by> b;     // Compiles iff /* = 42 */ is uncommented
j_random_hacker
This has got me thinking about policy classes more seriously. Thanks
Amol Gawai
You're welcome Amol :)
j_random_hacker