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., "&" 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!
|