Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
455 views
in Technique[技术] by (71.8m points)

design patterns - "Poor Man's Reflection" (AKA bottom-up reflection) in C++

I am implementing some rudimentary reflection in C++ for an ultra modular architecture where virtually all features are loaded as plugins and interpreted dynamically at run-time. Since there's a distinct element of structural self-organization to the system, components need some means of inspecting each other (for an example of a situation where this flavor of reflection is needed, see this question: “Best fit” dynamic type matching for plugins in C++).

The architecture has so far been developed in C#, but I am now looking into how it could be implemented in C++ instead. At this point I have created the skeleton for a "Poor Man's Reflection" based on the following model:

A Type class to hold relevant class information:

namespace Reflection {

    class Type {
    public:
        Type(Object &, string, bool(*)(Type *));
        ~Type();
        Object & RefObj();
        string Name();
        bool IsAssignableFrom(Type *);
    private:
        Object & _refObj;
        string _name;
        bool(*_isAssignableFrom_Handler)(Type *);
    };

}

and an Object class from which all participants in the reflection model will descend:

class Object {
public:
    Object();
    virtual ~Object();
    virtual string ToString();
    virtual Reflection::Type * GetType();
    static Reflection::Type * Type();
    static bool IsAssignableFrom(Reflection::Type *);
private:
    static Object _refObj;
    static Reflection::Type _type;
};

..which is defined like this:

string Object::ToString() { return GetType()->Name(); }

// all derived classes must implement the equivalent of this:

Reflection::Type * Object::GetType() { return &_type; }

Object Object::_refObj;

Reflection::Type Object::_type(_refObj, "Object", Object::IsAssignableFrom);

Reflection::Type * Object::Type() { return &_type; }

bool Object::IsAssignableFrom(Reflection::Type * type) {
    return dynamic_cast<Object*>(&type->RefObj()) != nullptr;
}

Note that I only need my reflectivity to operate within my own hierarchy of classes (that all inherit from Object). The above code therefore now enables me to:

  • Get the Type of any instance: instance.GetType()

  • Get the Type of any class: class::Type()

  • Compare Types: e.g. (instance.GetType() == class::Type()) or (instanceA.GetType() == instanceB.GetType())

  • Perform runtime checks to see if one Type instance can be assigned another (i.e. dynamically, with two "unknowns". All built-in options in C++ seem to require knowing at least one of the Types at compile time), essentially the equivalent of is and key to deducing inheritance relationships: (instanceA.GetType()->IsAssignableFrom(instanceB.GetType()))

  • Reference abstract types dynamically by their Type

  • Get a consistent, friendly name of a Type (i.e. class)

This is sufficient for my immediate needs, and the feature range can be extended by adding functionality to the Type class (next up is the ability to instantiate a class just given a Type instance - analog to .Net's Activator.CreateInstance). But unlike 'proper' reflection, which is essentially a top-down approach where [meta] information about classes is collected at the compiler level / managed centrally, this is done manually and bottom-up, distributing the knowledge into the objects themselves and giving them a way of communicating it between each other at run-time. So to make this work, every class included in this system needs to implement the same members and functions as the Object class, to encapsulate the relevant aspects of themselves for 'export'. Case in point, the Plugin class looks like this (definition):

Reflection::Type * Plugin::GetType() { return &_type; }

Plugin Plugin::_refObj;

Reflection::Type Plugin::_type(_refObj, "Plugin", Plugin::IsAssignableFrom);

Reflection::Type * Plugin::Type() { return &_type; }

bool Plugin::IsAssignableFrom(Reflection::Type * type) {
    return dynamic_cast<Plugin*>(&type->RefObj()) != nullptr;
}

As you can see, it's practically identical to its parent class, Object. Almost all of these functions vary only by their class type (and name).

So I have a couple of questions.

  • The first one is whether there any methods to streamline this, via compiler macros or clever inheritance / templating etc, since there is / will be so much repetition. It strikes me as something that could be automated? Like extracting out the name of the class (perhaps including namespaces) and just generate the code from that? Or some source code snippet template based on a variable or two (class name coming to mind).

  • The second is more general (and the reason I included all this code). I have only been working with C++ for a very short time, so I feel rather n00bish and assume that my approaches and implementation details could be very naive. If there are others that have worked on similar architectures / had similar needs, perhaps they could share their experience (or even just point out flaws in my model / code).

For an example of a situation where this flavor of reflection is needed, see this question: “Best fit” dynamic type matching for plugins in C++.

Update:

As far as the first question goes, here is what I ended up doing:

I made two macros, one for the .h file and one for the .cpp file. Now all I need to do is add REFLECTION_H(TYPE_NAME) in the class declaration and REFLECTION_CPP(TYPE_NAME) in the class definition and all the reflection boiler plate is automatically included. I can then just add class specific members as normal without having to think about reflection, knowing that all the plumbing required is in place and everything just works. For example, with the current implementation, a new Surface class would now look like this:

Surface.h:

class Surface : public Plugin
{
    REFLECTION_H(Surface)
public:
    // ...class specific public member declarations...
private:
    // ...class specific private member declarations...
};

Surface.cpp:

REFLECTION_CPP(Surface);

Surface::~Surface() {}

// ...class specific member definitions...

The macros are defined like this:

#define REFLECTION_H(TYPE_NAME) 
    public:
    virtual ~TYPE_NAME();
    static Reflection::Type& Type();
    private:
    virtual Reflection::Type& _getType();
    static TYPE_NAME _refObj;
    static Reflection::Type _type;
    static bool IsAssignableFrom(Reflection::Type&);
    static plugin_ptr CreateInstance();

#define REFLECTION_CPP(TYPE_NAME) 
    Reflection::Type& TYPE_NAME::Type() { return _type; }
    Reflection::Type& TYPE_NAME::_getType() { return _type; }
    TYPE_NAME TYPE_NAME::_refObj;
    Reflection::Type TYPE_NAME::_type(_refObj, #TYPE_NAME, true, IsAssignableFrom, CreateInstance);
    bool TYPE_NAME::IsAssignableFrom(Reflection::Type& type) { return dynamic_cast<TYPE_NAME*>(&type.RefObj()) != nullptr; }
    plugin_ptr TYPE_NAME::CreateInstance() { return plugin_ptr(new TYPE_NAME); }
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Just to summarize:

  1. there's a lot of existing industry-standard frameworks to implement loadable modules in C++, with different levels of module introspection. Just to name a few: MSWindows COM and variants, CORBA (various implementations), KDE KParts, DBus-enabled services on Linux and other Unix-like OSes etc. In general I would go with one of existing variant depending on target platform and other considerations

  2. If you absolutely need to costruct your own bike^W framework, I would separate classes implementing module business-logic from the boilerplate. Certainly this approach introduces another level of indirection and this may cause some performance issues. But if done clever this boilerplate may be quite thin, almost unperceivable. Also separating BL from the framework would allow to completely change the horse w/o much efforts in future. To go with this approach I would choose code manipulating tools like GCC-XML or appropriate CLang modules.

  3. Also there're a number of existing C++ libraries from simple to complex to build your own tightly composed framework. Examples: ROOT Reflex, Boost.Reflect

The rest is under your own choice. I know that people of Gnome project, unsatisfied with deficiencies and shortcomings of C++, invented their own OOP framework on plain C (GLib/GObject) and later developed on that basis a new fully functional language similar to C# (Vala). It's all up to you where to stop yourself :)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...