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 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 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 > _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.