Introduction
The original LibReflection written by axilmar has quite a few of the limitations that has been improved or relaxed in this version. This article will only describe the differences between the original and the new version. All of the existing functionalities and syntax are still supported and will work in the new version. Therefore, upgrade to a new version should be as simple as re-compiling the code. Reader should read the original LibReflection article before continuing.
The following items are changed/improved on the new version:
- Constructor information is now captured and stored within the
Class
object. Additionally, constructor can be invoked to create an object via the new
operator. When a default constructor is reflective into the Class
, the Class::newInstance()
method can be used as a shortcut to creating an instance. Example: using namespace agm::reflection;
using namespace std;
class Widget
{
CLASS(Widget, NullClass);
CONSTRUCTOR(public, Widget, ())
{
cout << "default constructor called" << endl;
}
CONSTRUCTOR(public, Widget, (int y) )
{
cout << "Widget(int) called with " << y << endl;
}
};
int main()
{
Widget w;
const Class & cls = w.getClass();
Widget* wp = static_cast<Widget*>( cls.newInstance() );
delete wp;
}
- In the original version, method argument type and return type are not captured. This has been changed. User can now retrieve type information (in the form of
std::type_info
) of the method argument and return. Using the class definition of Widget
above: const Class::ConstructorList & constructors = cls.getConstructors();
for (Class::ConstructorList::const_iterator iter = constructors.begin();
iter != constructors.end();
++iter)
{
const ConstructorMethod& cm = *iter;
if (cm.getArgsCount() == 1 && *cm.getArgsTypes()[0] == typeid(int))
{
cout << "Found Widget(int) constructor" << endl;
Widget *wp2;
cm.invoke(wp2, 12);
delete wp2;
}
}
- Class methods, static methods and constructors can be overloaded. User can search and iterate through all of the overloaded methods to select the one they are looking for. This is usually done by examining the argument type and returning the type of the method candidates. Example of overloading a constructor is already shown above. Static methods and class methods are defined similarly. Use of different macros is no longer required.
- Added simple Class registry. This allows users to list or search through the entire collection of "known" classes. Using the
Widget
example above: const Class * wcls = Class::forName("Widget");
if (wcls != 0)
{
cout << "Calling newInstance()" << endl;
Widget* wp = static_cast<Widget*>( wcls->newInstance() );
}
- Improved description on
TypeMismatchError
. When a method (whether it is a constructor, static method or class member method) is invoked with an incompatible type, the exception thrown will contain detailed information regarding which parameter is incompatible. Here is an example of a TypeMismatchError
exception thrown: terminate called after throwing an instance of
'agm::reflection::TypeMismatchError'
what(): static Label* Label::self(Label*):
(WARN only: type castable)return type mismatched: expected
(Label*) passed (Widget*);
Parameter 1 mismatched: expected (Label*) passed (Widget*);
- Full class name (including namespace) is now captured and can be queried via
Class::getFullName()
. Similarly, the method (constructor, static, member) now contains a new method getSignature()
which returns human readable class and argument type.
- Automatic casting of object class for compatible pointer and reference types. For example, if a method (constructor, static, member) takes a
Base *
, but a Derived *
is passed as the argument, the Derived *
will be automatically cast to Base *
. The original version will simply throw the TypeMismatchError
exception when the argument is not of exact type. This works for return values as well. The requirement is that both Base
and Derived
must be defined using reflection. class Label : public Widget
{
CLASS(Label, Widget);
CONSTRUCTOR(public, Label, ())
{
cout << "Label:: default constructor called" << endl;
}
STATIC_METHOD(public, Label *, self, (Label *that) )
{
cout << "The object type is " << that->getClass().getFullName() << endl;
return that;
}
};
int main()
{
Label l;
Widget* wp = &l;
Widget* wp_result;
l.getClass().getStaticMethod("self").invoke(wp_result, &l);
}
In this example, self()
is returning a Label *
but the result object passed is a Widget *
. The returning pointer is automatically cast.
- Dynamic type casting to its object class (down casting) for compatible pointer type. When a method expects a
Derived *
but the argument passed is a Base *
, the reflection library will attempt to cast the Base *
to Derived *
via the dynamic_cast<>
C++ operator. If the cast is not successful, TypeMismatchError
exception is thrown; otherwise the invocation will take place. Using the previous example: l.getClass().getStaticMethod("self").invoke(wp_result, wp);
- The LibReflection library now works in template classes as well. Templated method functions, however, can not be reflective. This is extracted from the example code in the template.cpp file:
template <class T> class Widget
{
CLASS(Widget, NullClass);
CONSTRUCTOR(public, Widget, ())
: storage()
{
cout << "default constructor called" << endl;
}
CONSTRUCTOR(public, Widget, (const Widget& x) )
: storage(x.storage)
{
cout << "copy constructor called" << endl;
}
CONSTRUCTOR(public, Widget, (const T& y) )
: storage(y)
{
cout << "Widget(T) called with " << y << endl;
}
METHOD(public, void, store, (T x))
{
cout << "calling store with " << x << endl;
this->storage = x;
}
virtual ~Widget() {}
private:
T storage;
};
template <class X, class Y> class Label : public Widget<X>
{
CLASS(Label, Widget<X>);
CONSTRUCTOR(public, Label, ())
: Widget<X>(), y_store()
{
cout << "default constructor called" << endl;
}
CONSTRUCTOR(public, Label, (const Label& x) )
: Widget<X>(x), y_store(x.y_store)
{
cout << "copy constructor called" << endl;
}
CONSTRUCTOR(public, Label, (const X & x, const Y & y) )
: Widget<X>(x), y_store(y)
{
cout << "Label(x,y) called with " << y << endl;
}
METHOD(public, void, store2nd, (Y y))
{
cout << "calling store2nd with " << y << endl;
y_store = y;
}
private:
Y y_store;
};
- Added support for dynamic reflection without
MACRO
. Any public class can be added to the LibReflection using the template class ReflectiveClass
. The resulting class defined in LibReflection is identical to the class added using the MACRO
method. This method of reflection is suitable for any class for which modification of class definition is impossible (such as vendor library). Using our beloved Widget / Label example, here is the same code defined using dynamic reflection: class Label : public Widget
{
public:
Label()
{
cout << "Label:: default constructor called" << endl;
}
static Label * self (Label *that)
{
cout << "The object type is " << that->getClass().getFullName() << endl;
return that;
}
};
static const ReflectiveClass<Label> label_clsdef =
ReflectiveClass<Label>((Widget*)0)
.constructor()
.static_method(&Label::self, "self");
The one minor difference is that because the class was un-modified, the getClass()
and getStaticClass()
are no longer part of the Label
class. The agm::reflection::Class *
can be retrieved by either label_clsdef.get()
or using the Class
registry Class::forType(const std::type_info&)
static member function.
- Added custom user defined macro. User can define macro
REFLECTION_TYPE_CUSTOMx
where x
is 1 to 4 before including the reflection header file. If defined, the macro should evaluate to a const std::type_info&
object. The custom type macro is useful if user must be able to lookup a particular class within a templated class. For instance, using smart pointer from boost library, we can have the following: #define REFLECTION_TYPE_CUSTOM1(C) typeid(boost::shared_ptr<C>)
. We can then test the corresponding type by using the Class::findType(const std::type_info& t)
. It is particularly useful when the exact type of the parameter is not known.
- Added support for placement instantiation (also known as placement new). The
ConstructorMethod
can be invoked using either invoke()
or invokePlacement()
which takes an extra void *
for placement of the object.
- Ported to GCC. This can be seen as a non-improvement as the new version has not been tested under MSVC or VS.NET.
Limitation (same as original):
- An instance of the class must be created before the class is "known".
- Single inheritance only and no virtual base class.
- Some of the implicit conversion originally done by the compiler will not be performed. E.g.:
long
-> int
, const char *
-> string
, etc.