Attributes
Throughout this article,
the definition of an "attribute" is a named reference to a variable. The
AttributeContainer system provides a way to create attributes by
"binding" them to a location in memory.
For example:
Container.Bind("player
name", &m_name);
In this example, the member variable ‘m_name' is bound to
the ID of ‘player name', which must remain unique within the ‘Container'
object. Clients of Container then can query the value of this variable at any
time by requesting that value from the container by ID.
name = Container.GetValue("player name");
The value of ‘m_name' can also be modified in the same
manner, ie:
Container.SetValue("player
name", "John Doe");
Yet attributes provide much more than glorified get/set
mechanisms.
AttributeContainer is a template class which requires some
knowledge of how to serialize the attributes it manages. This is done through
the Policy pattern
and is defined as:
template <class IO = AttributeContainerIO>
class
AttributeContainer : public IO
The included library provides an example implementation of
an XML I/O policy using TinyXML, but you can expand on these polices by
implementing your own IO classes. Since all of the methods related to
serialization are contained within the policy, and the AttributeContainer
itself knows nothing of such things, you are not locked into any specific interface
for serialization.
Defining an attribute container entails first implementing
your IO policy and then passing that policy into the container definition like
so:
AttributeContainer<AttributeContainerXmlIO>
I generally prefer to do this with a typedef:
typedef AttributeContainer<AttributeContainerXmlIO>
t_XmlAttributeContainer;
Objects that wish to Bind member variables to this
container, or to containers of this type, have a few choices. The safest way is for the object to inherit directly
from the container, ie:
class myObj : public t_XmlAttributeContainer
By inheriting directly from AttributeContainer, you avoid
situations where you might query a container for attributes of an object that
has already been deleted. While this is
the safest way, it does have one drawback, and that is memory usage.
AttributeContainer is not a huge class (see memory considerations), but if you
have thousands of instances of myObj, the overhead will add up quickly.
One alternative is to have a global instance of
AttributeContainer (or at least one that is in a larger scope than the objects
it manages). While this saves some memory, it does complicate use, as each
bound attribute must be somehow uniquely identified per object.
Also, access
into this global container must either be wrapped in checks to be sure the
object in question still exists, or it must be removed from the container
before the object is deleted. These hurdles can be easily solved by smart
pointers who automatically manage removing the object from any associated
containers or a number of other similar mechanisms.
However you choose to
implement the container object, once all of your attributes are bound to the
AttributeContainer, the container then can easily be used to serialize these
attributes.
For example:
Listing 1
class myObj : public t_XmlAttributeContainer;
{
public:
myObj() {};
int m_Int;
float m_Float;
double m_Double;
};
myObj::myObj()
{
Bind("my_int", &m_Int);
Bind("my_float", &m_Float);
Bind("my_double", &m_Double, ATTR_READ_ONLY);
}
Assume that I want to serialize all ‘myObj' objects to disk.
The t_XmlAttributeContainer object is a type of AttributeContainerXmlIO object, which implements the ReadXml and
WriteXml methods, so the following code sample is an example of how this
serialization might be done:
Listing 2
void myServer::Save()
{
TiXmlDocument doc;
TiXmlElement Xml("myObjects");
std::vector<myObj*>::iterator _i;
for (_i = m_objects.begin(); _i != m_objects.end(); _i++)
{
myObj* pObj = (*_i);
pObj->WriteXml(&Xml);
}
}
Every bound attribute of every myObj contained in the list
of m_objects has been persisted to xml with the exception of the ‘my_double'
attribute. This attribute was bound as ‘read only' and so was not written to
the xml stream.
The member objects of the example myObj object are all built
in types. What about complex types? AttributeContainer can bind and persist any
type of object, as long as the << and >> stream operators exist for
that type.
The example above bound member variables to a string id. But
what about temporary variables, or variables created on the stack or on the
heap? AttributeContainer provides safe ways to bind attributes regardless of
their storage or scope but, as any object that deals with pointers does, it
requires careful consideration.
|
After rereading the article, I see you mentioned this refactor potential:
"One alternative is to have a global instance of AttributeContainer (or at least one that is in a larger scope than the objects it manages). While this saves some memory, it does complicate use, as each bound attribute must be somehow uniquely identified per object."
There are certainly some not-too-difficult solutions that simplify the global shared container pattern for client code.
In a system I worked with, the following client code would serialize an object's properties to a stream:
Object *pObject = ...;
MutableString mutstr;
StringOutputStream sos(mutstr);
XmlSerializer ser(os);
ser.Serialize(pObject);