s11n.net
Taking the pain out of serialization.
Project powered by:
SourceForge.net

phoenix

A "phoenixing" monostate-like object provider for sharing objects within a "context type".

Of all of the classes i've written in my career, phoenix is probably the class i've gotten the most re-use out of. This page will attempt to show you what phoenix is all about.

The source code is available for download from the libs page. It is encapsulated in one header file, is very small, and has a one-function interface - it couldn't be simpler to include into your toolbox!

The following is from a mail sent to a developer friend of mine, martin krafft (of the Debian project). It summarizes the usage of phoenix and provides some examples of how i often use it.


Yo,
You once asked me about what the phoenix class is for...
i've got a good, short example to show you, which will hopefully show
you what i primarily use it for: serving static-style objects which are
shared amongst a specific context. Context, in this scope, is a *type*.
Consider this simple example:
Foo & shared_foo() {
static Foo bob;
return bob;
}
That's all fine and good, but:
a) this Foo object is shared globally. There is only one of them.
b) calling this post-main() has undefined results, because bob might be
destroyed by the runtime environment at any time.

Phoenix gets around both of these problems by:
a) providing a "sharing context", to allow multiple, independent Foos to
be shared in a singleton-like manner, without them being true
singletons. This is similar to the Monostate pattern, with the state
being shared in the given Context.
b) phoenix<>::instance() is post-main() safe because it can resurect bob
if bob is accessed after it is destroyed post-main(). e.g., this could
happen in app cleanup code, like a shared config object saving
~/.myconfig when it dies (this is what i first wrote the phoenix for,
actually).

Code says is most simply:
(this is real code, not an example created just for you. ;)
/** Internal marker class. */
template <typename T>
struct pool_sharing_context {};
/**
Returns the shared object pool for ObjectT objects.
*/
template <typename ObjectT>
pool::object_pool<ObjectT,uid_t> &
shared_pool()
{
typedef pool::object_pool<ObjectT,uid_t> PoolT;
typedef phoenix::phoenix<PoolT, pool_sharing_context<PoolT> > PHX;
return PHX::instance();
}
Now we have access to multiple, independent object pools:
pool::object_pool<Foo,uid_t> & foop = shared_pool<Foo>();
pool::object_pool<Bar,uid_t> & barp = shared_pool<Bar>();
This "context-sensitive sharing" is what i primarily gain from the
class. The additional benefit is that if shared_pool<X>() is called
post-main(), after the context-specific shared object_pool is destroyed
by the runtime environment, the phoenix will re-create the object at
the same address, using placement new. So, instead of crashing if it is
called post-main(), it will always return a valid object. The phoenix
has a 3rd (optional) template param which is a functor. This functor is
called when an object is created for the first time and when it is
phoenixed. This allows the object to be initialized using
client-defined logic, without requiring a specific ctor signature on
the phoenixed type. e.g.:
phoenix<Foo,Foo,foo_initializer>::instance();
foo_initializer might look like this:
struct foo_initializer {
void operator()( Foo & obj ) const {
obj.set_something( "whatever" );
... do whatever is necessary to populate obj ...
}
};
whenever the phoenix has to initialize a Foo object it will call
foo_initializer()( shared_foo ), which can then do things like populate
the shared object. libs11n makes extensive use of this to populate,
e.g., entity translation maps (e.g., "&amp;" to "&" for XML parsers).
Thus those maps are always guaranteed to be valid, even if called
post-main(). phoenix actually *subclasses* the type it serves, so it
always knows when that type is destroyed, and thus always knows when it
has to "phoenix" it.
There is no way to know what order objects get destroyed post-main(),
and the phoenix provides one way of getting around this problem. In
the case of the object_pool re-instantiating the object isn't as
useful as the entity maps, because the pool has no way of repopulating
itself once it's pooled objects are destroyed. In the case of char
entity translation maps it's trivial, though, and provides a good
safety net. AND... the translation maps aren't initialized until they
are actually used the first time, so it saves some resources in the
case that the object is never actually created. Thus we don't need to
create a static map<string,string> and populate it with
entities. Instead we delegate that to the initializer functor, and it
is initialized the first time it is used (and when it gets re-born via
phoenixing).
See ya!

That sums it up pretty nicely. A lengthy paper on the topic of the phoenix class - and on "context sharing" in general - can be found on papers page. That paper also details phoenix's history, motivations and inspirations.
martin's response had an interesting point about the phoenix, which i elaborated on in a later mail...


> also sprach stephan beal <stephan@s11n.net> [2004.06.13.1416 +0200]:
> Except it breaks normal object lifetimes. Check out
>
> http://www.gotw.ca/gotw/023.htm
>
> specifically problem #4.
That's UGLY! Evil!
The phoenix does nothing like that, actually, for 2 reasons:
1) it doesn't play with the copy ctor or assignment operator.
2) phoenix SUBCLASSES the served type, so it doesn't suffer the problem
of calling an incorrect dtor. There is no way to inject arbitrary
subclasses between the phoenix and the type it's serving, so no
incorrect dtor can be called. It calls it's *own* dtor, and that
inherently triggers the dtor of the object it's serving (the one it
subclasses). This also works when phoenix subclasses a type with a
non-virtual dtor, because we never delete() a phoenix: we rely on
static destruction. Inheriting from a type with a non-virtual dtor is
okay in and of itself. What's *not* okay is calling delete() on such a
type's pointer - undefined results. phoenix gets around that, though,
because phoenixes are *never* delete()ed.