Control Device: External Parameter Control

A very important question is the control of parameters in a program. Almost every nontrivial class contributing to the overall behavior of a program contains some parameters, that should be modifiable at startup or at runtime, e.g. the size of a data structure, filename for input and output etc. All these classes play the role of a controlable , that is, they have some parameters to be controled by some ControlDevice which is the counterpart. A ControlDevice could be, for example,

A controlable could be for example a simple class like

  class square : public controlable {
  private:
    double a, b; // sides
  public:
    square(ap = 0.0, bp = 0.0) : a(ap), b(bp) {}
    // need some method for pulling in parameters a, b from file

    // traditional solution:
    void read(istream& in);

    // 'controlable' solution: Let a ControlDevice know the details
    // and do the work
    virtual void register_at(ControlDevice&);
  };

The interfaces

The basic interface for controlable looks like this:
class controlable {
public:
  virtual void register_at(ControlDevice&,const string& prefix) = 0;
  virtual void register_at(ControlDevice&);
  virtual void notify()  {} // may be used to signal a change
  virtual ~controlable() {}
};

The "other" side (ControlDevice) which abstracts from the paramater input mechanism has the following outlook:

class ControlDevice  {
public:
  ControlDevice(control_device_impl* imp = 0) : impl(imp) {}
  void add(const string& nm, Mutator* value_ref);
  void update();
  string name() const;
  void print_values(ostream&) const;
  ControlDevice getSubDevice(const string& name);
private:
  control_device_impl* impl;
};

Now how does it work? A controlable will register_at(..) a given ControlDevice (e.g. via the method add(..) for basic parameters). This ControlDevice will then be able to change the parameters, for example upon a call to update() , which would in the case of a parameter-file cause the file to be read in. Where's the big deal of it? The advantage of this abstract approach is twofold: First, the code does not get littered with statements to read in parameters. Second, one gains flexibility in choosing the user interface: The implementation of the classes with parameters does not have to change, wether we have a GUI-interface, a simple parameter file or something completely different.

How does it work? Looking at the interface of ControlDevice , you see the function

  void add(const string& nm,Mutator* value_ref);
This is a name-value-pair (nm,value_ref ). The name is used to reference the right variable from the outside (e.g. in a file or as label in a window), the value reference is used to change the underlying variable. The interface of class Mutator follows:
class Mutator {
public:
  virtual void read (istream& in)  = 0;
  virtual void print(ostream& out) const = 0;
  virtual void print(ostream& out, const string& name) const = 0;
  virtual string description() const;
  virtual ~Mutator() {}
};

The value is actually changed via reading from a stream, which could be a string typed in at a window. A simple implementation of this interface is supplied by the following template:

template<class T>
class TypedMutator : public Mutator 
protected:
  T& v;
public:
  TypedMutator(T& vv) : v(vv) 
  T value() return v;
  vir1tual void read(istream& in)    in >> v;
  virtual void print(ostream& out) const 
     out << v;
  virtual void print(ostream& out, const string& prefix = "") const 
     out << prefix << v;
;

There are other Mutators, see the Mutator documentation. You can attach a (simple) Mutator to a variable like this:

ControlDevice Ctrl(GetFileControlDevice("myparameters.in"));
...
double x;
    Ctrl.add("x",GetMutator(x));
    // RegisterAt(Ctrl,"x",x) is a synomym for the above.

The file myparameters.in could then contain the name-value-pair x 0.43 , and on Ctrl.update() the variable x would be set to 0.43 . This example also shows how to create a ControlDevice More examples of how this works are found in ex1.C together with a sample input file. It shows also how to use other types of Mutators and how to build hierarchical (i.e. nested) name-value-structures.


Guntram Berti