Mirror reflection library 0.5.13

Reflection basics

This is a very short introduction into reflection and reflective programming theory.

Introduction

Generally speaking reflection is a mechanism, which makes possible for an entity to investigate and change itself. In computer science, reflection refers to the ability of a computer program to examine and modify its own structure and behavior. This includes building new or altering the existing data structures or if possible also the program code. The programming paradigm driven by reflection is called reflective programming. It is a particular kind of metaprogramming.

Metaprogramming is usually a more general term where a program creates, examines and/or manipulates a different program, i.e. not itself. Reflection-oriented programming includes self-examination, self-modification, and self-replication, which can be (depending on the programming language) done at compile-time and/or run-time.

Native support for reflective programming is most common in high-level virtual machine programming languages like Java, C#, CLOS, Smalltalk, etc. and in interpreted scripting languages. Reflective programming is less common or limited in statically typed programming languages like C or C++. Because of this, dynamic or run-time reflection is more common. Besides native support by the language itself, reflection can be added by the means of a library.

One of the basic functions of a reflection facility is to provide meta-data describing the program's structure and code. This meta-data can be then used in various programming tasks. Furthermore a reflection facility may provide higher-level functionality based on this meta-data.

Various implementations of reflection and meta-programming facilities can be classified by their support for:

Support for reflection in C++

Although C++ has a huge generic programming and meta-programming potential (as demonstrated by some of the Boost Libraries, STL and many other libraries), C++ implementations (conforming to the ANSI/ISO standard) have only a very limited native support for reflection. Prior to the C++0x standard, basically the only reflection mechanism was the RTTI (Run Time Type Identification). RTTI allows to identify types at both compile-time and run-time (the latter is valid only for classes with virtual member functions), do comparison between types, and query some very basic information about these types.

The C++0x standard introduced some other useful features like the decltype operator and the auto type specifier. Furthermore the standard library now contains several useful type traits - template classes for inspecting various traits, categorizing and modifying types.

Various compilers and development environments provide additional non-standard extensions for reflective programming. Prior to C++0x many compilers supported for example some version of the typeof / decltype operator. Another example may be the Borland's C++ Builder RAD tool that introduced several basic reflection facilities, like the __classid operator, the TMetaClass type, etc., which allow to perform basic object inspection, and other things. These usually serve for internal purposes of the IDEs, and developers are discouraged to use them directly. The main problem of these extensions is that they are not portable.

C++ has strong type checking and is considered to be rather a static language. This can be very limiting in the terms of data-structure modifications during run-time. Furthermore C++ programs are not running inside of a virtual machine, but are compiled into executable binaries and therefore run-time modification of the code is generally not possible, without re-compilation.

Design principles for meta-programming facilities

Similar to the basic principles of object-oriented programming, there are also three principles that are attributed to well designed reflection and meta-programming architectures:

Today most of the mainstream implementations of the reflection facilities (usually referred to as traditional reflection facilities) do not follow these principles. The one of the common issues is, that in the traditional reflection facilities the meta-data is tightly tied to the base-level data, usually by deriving all predefined and user-defined classes from a single ultimate base class, that has several virtual member functions which provide meta-data about the concrete instance for which they are called and also about its type.

The problem with this approach is that it violates the rule of stratification, because the meta-level objects cannot be detached from the base-level objects. Thus in applications where reflection is not needed it still must be included. This can enlarge the footprint of the application, which can be a problem for example in mobile applications, where storage and memory resources are limited.

  // definition of a meta class' interface by the reflection facility
  interface iMetaClass
  {
    virtual string getName(void) = 0;
    //...other member functions providing meta-data about the reflected class
  };

  // a special common (implicit) base class for every other class
  class Object
  {
    iMetaClass* getClass(void) = 0;
    // other members
  };

  // user defined class implicitly derives from Object
  class myClass [ : public Object ]
  {
    // class' members ...
    // automatically generated implicit members possibly
    // using some variant of covariant return type
    MetaClass<myClass>* getClass(void){return new MetaClass<myClass>();}
  };

  myClass* myInst1 = new myClass;
  // use of the reflection facility
  // reflection is inherent part of the base-level objects
  std::cout << myInst1->getClass()->getName() << std::endl;
  //
  // PROBLEM: this won't work because the intrinsic types don't
  // inherit from Object
  double myInst2;
  std::cout << myInst2.getClass()->getName() << std::endl;

Another approach is to separate the meta-objects from the base-level objects and tie them only on demand by special reflection functors, sometimes called mirrors.

A simple example is shown in the following pseudo-code sample:

  // definition of a meta class' interface by the reflection facility
  interface iMetaClass
  {
    virtual string getName(void) = 0;
    //...other member functions providing meta-data about the reflected class
  };

  // reflection functor returning the meta-data about a class
  template <Class> iMetaClass* reflect_class(void);

  // user defined class. no common base necessary
  class myClass
  {
    // class' members ...
    // none of them reflection-related
  };

  // use of the reflection facility
  std::cout << reflect_type<myClass>()->getName() << std::endl;
  // no problem here either
  std::cout << reflect_type<double>()->getName() << std::endl;
 // nor here
  double myInst;
  std::cout << reflect_type<decltype(myInst)>()->getName() << std::endl;

Sources of meta-data

In languages which support reflection inherently, there is no need to solve the problem of how to get the meta-data, reflecting the base-level objects. The source is the program compiler or interpreter. However in custom built reflection APIs, one needs to find alternate means how to collect meta-data. Some of the options usable in a C++ reflection facility are presented in the following list:

Generally when implementing reflection in a language without inherent support one should use a suitable combination of the mentioned options.


Copyright © 2006-2011 Matus Chochlik, University of Zilina, Zilina, Slovakia.
<matus.chochlik -at- fri.uniza.sk>
<chochlik -at -gmail.com>
Documentation generated on Fri Dec 16 2011 by Doxygen (version 1.7.3).
Important note: Although the 'boostified' version of Mirror uses the Boost C++ libraries Coding Guidelines and is implemented inside of the boost namespace, it IS NOT an officially reviewed and accepted Boost library. Mirror is being developed with the intention to be submitted for review for inclusion to the Boost C++ libraries.