|
Boost Users : |
From: Robbie Morrison (robbie_at_[hidden])
Date: 2007-10-17 14:10:37
Hi all
The following kind of code is probably quite common.
It involves:
* using individual Register objects containing
construction parameters to help factory-create new
Entity objects
* and, in the process, "tying" key object state
variables so that each associated Registry object
can watch these
And yes, I know this breaks encapsulation!
I chose C++ references and my code seems quite
complicated for what should be a relatively
straightforward task. Pointers were rejected because
the dereferencing syntax can lead to really obscure
numerical expressions (particularly for non-C/C++
programmers).
However, boost::ref() seemed to offer a simpler
solution and I experimented with this for quite some
time to no avail. I also considered using a
boost::ptr_vector and 1-tuples.
In addition, adding cout statements to ctors and dtors
led to more activity than I would have expected.
Moreover, there were more dtor calls than ctor calls.
Any comments or suggestions gratefully received!
Also an explanation of the differences between a "type
conversion" operator and an "address-of" operator. Is
this simply related to role or is there a distinction?
More information follows the program listing.
many thanks in advance
Robbie
--- Robbie Morrison PhD student -- policy-oriented energy system simulation Institute for Energy Engineering (IET) Technical University of Berlin (TU-Berlin), Germany [from IMAP client] // --------------------------------------------------------- // Copyright: (c) 2007 Robbie Morrison. All rights reserved. // License: Unrestricted reuse in any form, for any purpose. // Version: $Id: frag-xeona-registers-9.cc,v 1.3 2007/10/17 15:23:43 robbie Exp $ // Tested using g++ 4.1.2 (-Wall -Weffc++ -pedantic), boost // 1.34.1 (source build), and valgrind (memory checker) on // Ubuntu 6.10 (7.04 is current) using Linux 2.6.17-12-generic. #include <iostream> // standard io #include <string> // C++ strings #include <vector> // STL sequence container #include <iomanip> // setw() and family #include <tr1/memory> // TR1 smart pointers using std::tr1::shared_ptr; // for convenience // --------------------------------------------------------- template <typename T> class Value // just for type conversion operator T&() { public: explicit Value(T v) : _value(new T(v)) { } operator T&() { return *_value; } // return aliasable value private: shared_ptr<T> _value; // pointer usage is essential }; // --------------------------------------------------------- class Register // initializes and watches selected { // .. Entity variables public: explicit Register() : _vec() { } // explicit for testing template <typename T> // would normally support int,double,bool Value<T> tieRef(const std::string& tag) // would normally search on tag! { T temp = 0.45; // would normally obtain value from user shared_ptr<Value<T> > val(new Value<T>(temp)); // actual object is shared _vec.push_back(val); // crude "last on" indexing for testing report(); return *_vec.back(); // given aforementioned "last on" indexing } void report() // just for doubles (could be templated) { double temp = *_vec.back(); // this line is interesting! std::cout << "Register : value : " << temp << std::endl; } private: std::vector<shared_ptr<Value<double> > > _vec; // data mirror }; // --------------------------------------------------------- class Entity // represents simulation 'things' { public: explicit // explicit for testing Entity(Register& r) : // pass-by-reference necessary _register(r), // for testing purposes _coeff(r.tieRef<double>("coeff")), // intuitive syntax _notInteresting() // rely on default construction { report(); } void modify(double d) { _coeff = d; // normal syntax (no pointer derefs) report(); // display this modification _register.report(); // confirm the modification propagated } void report() { std::cout << "Entity : value : " << _coeff << std::endl; } private: Register& _register; double& _coeff; // watched variable int _notInteresting; // ignored variable }; // --------------------------------------------------------- int main(int argc, char* argv[]) { std::cout << std::fixed << std::setprecision(2); std::cout << " creating a register" << std::endl; Register reg; std::cout << " creating an entity" << std::endl; Entity ent(reg); std::cout << " modifying the entity" << std::endl; ent.modify(0.01); ent.modify(2.0); } // ---- end-of-program ------------------------------------- // OUTPUT // creating a register // creating an entity // Register : value : 0.45 // Entity : value : 0.45 // modifying the entity // Entity : value : 0.01 // Register : value : 0.01 // Entity : value : 2.00 // Register : value : 2.00 // Development environment // // boost : 1.34.1 (via ./configure; make all; make install) // gcc : 4.1.2 20060928 (prerelease) (Ubuntu 4.1.1-13ubuntu5) // os : Ubuntu 6.10, Linux 2.6.17-12-generic (7.04 is current) // hardware : Toshiba Tecra A2 330 laptop (purchased 27-Aug-2004) // specs : 1.4GHz Intel Celeron M (32-bit) / 512MiB RAM / 40GB HDD // // Problem // // The problem involves sharing data between Entity objects, // used to represent various 'things' in my simulation -- // and Register objects ('register' in the sense of a book // recording important information), used to help construct // and then watch the state of Entity objects. A concrete // ObjectFactory object (not relevant to this discussion) // will be used to create the Entity objects (Alexandrescu // 2001 pp179-217). // // I would also like a relatively clear syntax for // programming the Entity's as there are lots of these and // they contain numerical expressions -- for instance, // dereferencing (smart) pointers would yield lines like: // // *_perf = 2.1 * local * *_coeff; // // Solution (provisional) // // The chosen strategy is to declare all to-be-observed // Entity object data members as references and then bind // these to the relevant Register object element upon // construction. // // An alternative approach could be to utilize the Memento // pattern (which relies on friendship) in conjunction with // the Iterator pattern (for traversing a population of // objects) (Gamma etal 1995 pp283-291 and pp257-271 // respectively). // // Design notes // // In relation to C++, a reference data member must be // initialized in the constructor's initializer list // (Lischner 2003 p36). And a reference may not be bound to // a class member (data member or member function) (p35). // // Class Value<T> is instead used to avoid an "invalid // initialization .. from a temporary" error when // initializing Entity::_coeff using the Register::tie<T> // member function. // // Class Value supplies a "type conversion operator" // (Lischner 2003 p112), in this case, Value::operator T&, // which binds to the non-const reference data member in // Entity. The code for Value derives from Lischner (2003 // p119 example 5-21). // // References // // Alexandrescu, Andrei. 2001. Modern C++ design : // generic programming and design patterns applied. // Addison-Wesley, Boston, USA. ISBN 0-201-70431-5. // // Gamma, Erich, Richard Helm, Ralph Johnson, and John // Vlissides. 1995. Design patterns : elements of // reusable object-oriented software. Addison-Wesley, // Boston, USA. ISBN 0-201-63361-2. // // Lischner, Ray. 2003. C++ in a nutshell : a language // and library reference, O'Reilly and Associates, // Sebastopol, California, USA. ISBN 0-596-00298-X.
Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net