Metaclasses

Home Page Narrow Gate Logic !>Jonatan*PL cattery<!  kurs "Filip"  Mail me

Home ] Up ] What's new ] Downloads ] Interests ] Education ] Projects ] Programming ] Arts ] Journeys ] Photos ] Jokes ] Friends ] Links ]

 

The problem

Solution

Source

Example

Other issues

Metaclasses

The problem

Have you ever written some code in Delphi or C++Builder? If yes, you might have encountered TClass or TMetaClass types. TMetaClass is a class of which this pointer points to the vtable. And TClass is a reference(pointer) to TMetaClass.

Anyway, the conception of classes to be objects is very nice. For Smalltalk programmers it's nothing new, but in C++, which is not pure object language, this is something interesting. 

But the main question is, what it can be used for. Well, mostly for creating objects of unknown type for creator. You just have to pass the type variable during run-time, and the function can create many instances of that objects. 

Note that it is essential that a type variable has to guarantee that the objects of the type are inheriting from some common ancestor. Also it is important that type variables are resolved at run-time, not at compile-time, like template typenames.

Solution

The solution is templates, of course. Also, we'll use the possibility of overriding virtual functions with wider return type. 

The main problem during the projecting the classes was that all classes are of some class, so they should be derived from some common ancestor. But, from the other hand, such a metaclass would be able only to return new instances of type void, what would disallowed to return more specific types in derived classes. So, I have decided to create separate root metaclass for each of root classes. So, that's how it looks (you may also want to look at the source):

template<class T, class I = mcstubmc, class t = mctraits<I> >
class metaclassdef : public metaclass<I, t> {
    public:
        virtual T *newinstance() const { return new T; }
        virtual T *newinstance(unsigned count) const { return new T[count]; }


        // for metaobjects
        template<class D, class E>
            bool inheritsfrom(const metaclass<D, E> *co) const { return dynamic_cast< const metaclass<D>*>(this); }
        // for objects
        template<class D>
            bool objectisa(const D *co) const { return dynamic_cast< const metaclass<D>*>(this); }

        virtual const char *name() const { return typeid(T).name(); }
        virtual const type_info &typeinfo() const { return typeid(T); }
};

The T parameter is the type that we want to define metaclass of, and I is type of it's immediate ancestor. Traits and mctraits type is used by  metaclass type to tell what is the actual type of metaclass.  Functions newinstance are clear, I guess. The inheritsfrom function returns true if metaclass passed as a parameter is an ancestor of this type. And the objectisa can be called to obtain information if passed object is of class denoted by the metaclass.

That what I showed you above is only a definition of metaclass. In each class there should be an instance of appropriate metaclass (this is quite obvious - instances of metaclasses are classes) defined as static member. Also, it wouldn't be convenient having to write each time you want to reference a metaclass all of it's immidiate ancestors. So, we typedef a metaclassdef type inside a class, and then use it in form T::metacls, where T is the type of which metaclass we want to obtain. In case you wanted to reference that type using more standard convention metaclass<T>, there is such template class defined, that is derived from the member typedef of T class (T::metacls). Because there is no template typedefs (yet), the class hierarchy is more complicated. Anyway, mctraits type is used to tell what is the name of that member  typedef. 

Also, there is a particular type for classes that have no ancestors. The difference is only that it is not derived from any class. There is also a type which inherits from two ancestors. It is quite simple to add metaclasses that inherit from more than this.

To simplify the defining of needed members there are macros defined. This is one of it:

#define metaobject(T, C) \
    public: \
        typedef metaclassdef< T, C > __metadef; \
        typedef metaclass< T > metacls; \
    private: \
        static const metacls __tp; \
    public: \
        static const metacls *meta() { return &__tp; } \
    private:

 This is the one for class that inherits from one class only. Firstly, metaclass definition is exported for metaclass<T> class. Then, typedef for use simplified use of metaclass type is defined (T::metacls). The __tp variable is the instance of metaclass. And the last declaration is function used to access that instance.

To define the instance there is also a helper macro named metaobjectdef, the only parameter is the name of class.

Source

The full source of metaclasses header file you is available here.

It includes a commented example of use.

Example

class a {
    metabaseobject(a)
    public:
        a () { cout << "a::a \n"; }

        virtual void prn() { cout << "a"; }
};


class b : public a {
    metaobject(b, a)
    public:
        b () { cout << "b::b \n"; }

        virtual void prn() { cout << "b\n"; }
};


class d {
    metabaseobject(d)
    public:
        d () { cout << "d::d \n"; }

        virtual void prn() { cout << "d\n"; }
};


class c : public d, public b {
    metaobject2(c, d, b)
    public:
        c () { cout << "c::c \n"; }

        virtual void prn() { cout << "c\n"; }
};

metaobjectdef(a);
metaobjectdef(b);
metaobjectdef(c);
metaobjectdef(d);

void main() {
    const metaclass<a> *ma = a::meta();
    ma = c::meta();
    a *p = ma->newinstance();
    p->prn();

    delete p; 


    p = (b*)new c;
    p->prn();

    cout <<a::meta()->inheritsfrom(c::meta());
    cout <<c::meta()->inheritsfrom(a::meta());
    cout <<b::meta()->objectisa(p) << endl;
    cout <<a::meta()->typeinfo().name() << endl;
    cout <<ma->typeinfo().name() << endl;

    delete p;
}

 

The output from this program should look like that:

d::d
a::a
b::b
c::c
c
d::d
a::a
b::b
c::c
c
011
metaclasses::a
metaclasses::c

It was run and tested under Borland BCC 5.5 free compiler.

Other issues

The thing that might be interesting to add to this library would be a dynamic methods (like in Java or C++Builder). They would be called by name. Of course, it won't look as pretty as in Java, but we have to remember that C++ is a statically-typed language - but, if someone would really like to, he could overload the comma operator, and even define constants for strings denoting methods (who knows, maybe I will try to implement this).

The only thing we have to do is to keep a list of dynamic methods in metaobjects, and add a method to the classes that want to use it. That method would simply pass the request to call function with specified name on specified object. Clear and simple.

And at the end, the problem. The thing is the metaclasses doesn't allow to call any constructor during dynamic creation of new object. We could use templates, but, unfortunately, virtual template functions are not allowed. So, you would have to define class's "virtual constructors" in the class derived from class's metaclass, and then use the new metaclass in the __metadef typedef.

If you have any questions regarding this library, especially, if you have found some errors, just email me.

 

Home Page Top of page Back ] Up ] Next ]

Up ]

Page created 2001-03-17 17:02:36, last edited 2003-11-10 12:44:51   by Marcin Wudarczyk