Reflect Usage
From Nocturnal Initiative
Contents |
Nocturnal::SmartPtr<>
Reflect makes wide use of the standard C++ heap, and manages its allocated objects using reference counting. The SmartPtr template is used to track instances, and is similar in function to boost's instrusive_ptr. It relies on the object it references to store its own reference count. You will frequently see a class' SmartPtr typedef'ed to be the class name appended with "Ptr":
typedef Nocturnal::SmartPtr< class Foo > FooPtr;
Declaring an Element Class
Here is an example of a simple Reflect Element:
class Example : public Reflect::ConcreteInheritor<Example, Reflect::Element> { public: Example () { } private: std::string m_Name; static void EnumerateClass( Reflect::Compositor<Example>& comp ) { comp.AddSerializer( &Example::m_Name ); } }; typedef Nocturnal::SmartPtr<Example> ExamplePtr;
Three important things are happening in this class declaration:
- Example is using Reflect::ConcreteInheritor to implement its virtual API. The inheritor template implements virtual functions used for type checking and factory creation. If we wanted Example to be an abstract base class, we could have used Reflect::AbstractInheritor.
- EnumerateClass is implemented as a static function that takes a compositor template. Reflect::Compositor greatly helps creating reflection information for Example, and in this case it will deduce the correct Serializer type for m_Name (Reflect::StringSerializer). Its interesting to note that AddSerializer is actually a macro. AddSerializer stringifies the macro parameter and calls Reflect::Compositor<>::AddNamedSerializer, which will remove the ampersand and class decoration from the field name.
- As a matter of convention we typically typedef a Nocturnal::SmartPtr<> for this class for brevity when using Example objects in client code.
Registering an Element Class
In order to fully utilize your class you must register it in an initialization function of your library or program:
i32 g_InitCount = 0; bool Initialize() { bool success = true; if ( ++g_InitCount == 1 ) { success &= Reflect::Initialize(); // initialize and increment the reference count of Reflect Reflect::Registry::GetInstance()->RegisterType( Example::CreateClass() ); } return success; } void Cleanup() { if ( --g_InitCount == 0 ) { Reflect::Cleanup(); // decrement a reference and potentially cleanup Reflect } }
Nocturnal reference counts the initialization state of it's libraries to avoid duplicate work and support multiple modules requiring a shared module needing initialization and cleanup. Its very important that you call Reflect::Cleanup before you program returns from main, or you may see a lot of memory as being leaked by a memory leak detector. This apparently leaked memory may be the Reflect Registry. Calling Reflect::Cleanup for every call to Reflect::Initialize will prevent this from happening by correctly releasing the memory the Reflect Registry owns.
Serializing an Element Class
Now you have a class declared, you can instantiate and persist that object using Reflect::Archive:
int main() { bool success = true; // register Example success &= Initialize(); if ( success ) { ExamplePtr example = new Example (); example->m_Name = "Example"; // IRB is an Insomniac Reflect Binary file, it could also be .xml or .irx std::string filePath = "example.irb"; try { example->ToFile( filePath ); // write 'example' to the file } catch ( const Nocturnal::Exception& ex ) { Console::Error( "Unable to write file '%s'\n", filePath.c_str() ); success = false; } example = NULL; // throw this instance away try { example = Archive::FromFile< Example >( filePath ); // read a new instance of Example from the file } catch ( const Nocturnal::Exception& ex ) { Console::Error( "Unable to read file '%s'\n", filePath.c_str() ); success = false; } if ( !example.ReferencesObject() ) { Console::Error( "Failure reading file '%s'\n", filePath.c_str() ); success = false; } // unregister Example and release all the memory used by Reflect Cleanup(); } else { Console::Error( "Failed to initialize Reflect\n" ); } return success ? 0 : 1; }
Type Casting Objects
For these examples, assume:
Element
-> Foo -> Bar -> Baz
So 'Baz' is a 'Bar', but not a 'Foo'.
Note: All these casting functions will work with a bare pointer (Example*) or Nocturnal::SmartPtr<> (ExamplePtr).
Reflect::TryCast
TryCast will throw an exception if the object is not compatible with the desired type:
ElementPtr e = new Foo (); // this will throw an exception derived from Nocturnal::Exception BazPtr b = Reflect::TryCast< Baz >( e ); e = new Baz (); // this will type check and return a non-null pointer b = Reflect::TryCast< Baz >( e );
Reflect::ObjectCast
ObjectCast will return NULL if the object is not compatible with the desired type:
ElementPtr e = new Foo (); // this will always return NULL BazPtr b = Reflect::ObjectCast< Baz >( e ); e = new Baz (); // this will type check and return a non-null pointer b = Reflect::ObjectCast< Baz >( e );
Reflect::AssertCast
In debug builds AssertCast will assert on a successful type check, and then call DangerousCast. In release AssertCast simply calls DangerousCast.
ElementPtr e = new Foo (); // this will assert in debug builds, but cause damage in release builds BazPtr b = Reflect::AssertCast< Baz >( e ); e = new Baz (); // this will skip the type check in release builds, but will return a correct pointer in this example b = Reflect::AssertCast< Baz >( e );
Reflect::DangerousCast
DangerousCast does not do any type checking, ever. Use at your own risk. It exists only to cast in circumstances where we know with certainty that the instance is compatible with the desired pointer.
ElementPtr e = new Foo (); // this will always cause damage BazPtr b = Reflect::DangerousCast < Baz >( e ); e = new Baz (); // this will always skip the type check, but will return a correct pointer in this example b = Reflect::DangerousCast< Baz >( e );
