Reflect Usage

From Nocturnal Initiative

Jump to: navigation, search

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:

  1. 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.
  2. 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.
  3. 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 );
Personal tools
Navigation
projects