Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An update to AGM::LibReflection: A reflection library for C++

0.00/5 (No votes)
11 May 2005 2  
This article is an update to the original AGM::LibReflection library.

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:

  1. 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;
    }
  2. 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;
            // we will look for Widget(int) constructor
    
            if (cm.getArgsCount() == 1 && *cm.getArgsTypes()[0] == typeid(int))
            {
            cout << "Found Widget(int) constructor" << endl;
            Widget *wp2;
            cm.invoke(wp2, 12);
            
            delete wp2;
            }
        }
  3. 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.
  4. 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() );
        }
  5. 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*);
  6. 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.
  7. 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);
        /* wp_result pointer to a Label * object */
    }

    In this example, self() is returning a Label * but the result object passed is a Widget *. The returning pointer is automatically cast.

  8. 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);
        /* wp is of type Widget *, but the argument requires a Label * */
  9. 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;
    };
  10. 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;
        }
    
    };
    
    // dynamic class reflection to LibReflection
    
    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.

  11. 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.
  12. 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.
  13. 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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here