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&); };
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.