Reflect child classes from base class in C++

We want a solution in C++ that must be able to do the following:
Given a string of particular type, lets say 'A', we want to find all the types that derives from 'A'.
Instantiate new objects out of the types that are derived from 'A'.
E.g. Lets say we have a class, VehicleEntity. VehicleEntityhas child classes, PassangerCarEntity, TruckEntity, TrainEntity, BoatEntity.
We are unsure what vehicle entities there may be as the a library could be added containing more VehicleEntities. E.g. an AirplaneEntity thaterives from VehicleEntity could be added after deployment.
In the application, when a user wants to select a VehicleEntity, the user should be able to pick any of the entities deriving from VehicleEntity. This includes the PassangerCarEntity, TruckEntity, TrainEntity, BoatEntity and AirplaneEntity.
The user selects an Entity, lets say AirplaneEntity, A new object of type AirplaneEntity must be instantiated.

The following is an concept example in C# of what we want to achieve in C++.
In C# the items for the dropdown list can be retrieved as follows:
1
2
3
4
5
6
7
8
9
10
11
Type vehicleEntityType = typeof(VehicleEntity);
List<Type> types = new List<Type>();
foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
    types.AddRange(assembly.GetTypes().Where(x => vehicleEntityType.IsAssignableFrom(x) && !x.IsGenericType && !x.IsAbstract));
dropDownBox.ItemList = types;

//To instantiate the object:
List<VehicleEntity> vehicleList = new List<VehicleEntity>();
Type newVehicleType = (Type)dropDownBox.SelectedItem;
object newVehicle = Activator.CreateInstance(newVehicleType ); // defrault constructor used, parameterless constructors for vehicles.
vehicleList.Add(newVehicle);


We are aware that standard C++ does not contain any Metadata on its objects, and thus it is not possible without a workaround. It does not seem possible with RTTI and boost.Mirror.
I am sure that we are not the only ones that had this problem and that solutions exist.
Last edited on
The exemplar idiom (C++ implementation of the prototype pattern) is one of the commonly used ways:

'Advanced C++ Programming Styles and Idioms' by Coplien (chapter 8)
http://www.amazon.com/Advanced-C-Programming-Styles-Idioms/dp/0201548550

http://www.oodesign.com/prototype-pattern.html

http://www.cs.sjsu.edu/~pearce/modules/lectures/oop/types/reflection/prototype.htm
Thank you JLBorges, especially for the book reference.

We have considered the prototype pattern approach, it is the only means we could find to achieve this without reflection. (An thanks again, I was unaware this approach was a pattern.)

For the next pragraph, the terminology in: http://www.cs.sjsu.edu/~pearce/modules/lectures/oop/types/reflection/prototype.htm was used
The disadvantages we face with this approach is the extra maintainance costs and development labour incurred creating and maintaining the prototypeTable on the prototype class ( / prototype manager). We considered automatic run-time population of the prototable. Where the concretePrototype will update the prototype's table when it is constructed.
This will however not suffice, as there will most probably be a lot of incidents where the concretePrototype is requested through the prototype without ever having constructed the concretePrototype beforehand. Thus, there is no entry of it in the prototype's prototable. Meaning this list needs to be maintained manually.

This is quite a big quality requirement in our product. Not only for our very small development team, more so for our clients which will have to deal with this when extending the application. This will make the learning process more complex and tediuos as well as making the development take longer and more error prone (neglected to update the prototype's prototable). We perceive these as important qualities of the system and we hope that this is not the only solution.
> This will make the learning process more complex and tediuos as well as
> making the development take longer and more error prone
> (neglected to update the prototype's prototable).

Quite a bit of syntactic sugar can be provided by the writer of the library.

For instance, the CRTP technique described here can be extended to work with Coplien's exemplar mechanism (chain of responsibility pattern used for creation of objects) to facilitate automatic instantiation and registration of the prototype object.
http://katyscode.wordpress.com/2013/08/22/c-polymorphic-cloning-and-the-crtp-curiously-recurring-template-pattern/
isn't it coming under object factory? http://www.codeproject.com/Articles/567242/AplusC-2b-2bplusObjectplusFactory

and some fast example:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class parent
{
public:
    int a;
    virtual void test()
    {
    }

    virtual ~parent()
    {
    }
};

class child1 : public parent
{
public:
    int b;
    virtual void test()
    {
	b=7;
    }
};

class child2 : public parent
{
public:
    int c;
    virtual void test()
    {
	c = 6;
    }
};

int main()
{
    parent* list[3];

    list[0] = new child1;
    list[1] = new child2;
    list[2] = NULL;

    for (int i=0;i<3;i++)
    {
	cout << "list " << i << " is : ";

	if (child1 *pc = dynamic_cast<child1*>(list[i]))
	    cout << "child1" << endl;
	else if (child2 *pc2 = dynamic_cast<child2*>(list[i]))
	    cout << "child2" << endl;
	else
	    cout << "neither child1 or child2" << endl;
    }

    delete list[0],list[1],list[2];

    system("pause");
    return 0;
}


adding new child would require to add new type to detect.

Hope this is what you needed.
Last edited on
JLBorges, you are full of patterns. It inspires me.

It is a very nice and very low maintenance solution.

It nearly solves my problem. There is only issue one issue that is standing in my way still.
All the types should be registered on application startup:
- Most of the types will be accessed for the first time when the user request a list of the types.
- These types will be constructed for the first time when the user, for the first time, selects the type, say from a drop down list, to be constructed.

With the prototype pattern, these types are only registered once it is constructed in an object. We need it before construction.

Some static object solution for the registration process would also not work as the type needs to be accessed before the type is registered. The types not yet being used in the application hasn't accessed in the application yet, and thus, has not been registered. It will not appear in the drop down list.

Having some class in an application being aware of all the types in an application before any type has been constructed or accessed seems to me impossible without some compile time magic.
> All the types should be registered on application startup:
> Most of the types will be accessed for the first time when the user request a list of the types.

We can exploit the C++ guarantee that a derived class constructor would invoke the constructor of the base class. If an exemplar object with a static storage duration is instantiated at namespace scope, that object is constructed on application start up. We just need to make sure that we can distinguish between the calls to the base class constructor for normal objects and the call to the base class constructor for exemplar objects. In the base class coloured constructor (overloaded constructor for exemplar objects), we can automatically perform the registration. This technique is described in detail in Coplien's book.

What I had indicated in my previous post is that this can be combined with CRTP to make the whole mechanism transparent to derived class authors. If some of these derived class authors would be programming with very little (or no) awareness of this elaborate mechanism, we can provide a couple of pre-processor macros which hide the implementation details even further.

Something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/////////////  header file vehicle.h //////////////////
#include <iostream>
#include <string>
#include <memory>

struct vehicle
{
    public:

        using pointer = std::shared_ptr<vehicle> ;

        static pointer create_vehicle( const std::string& id ) ;

        // polymorphic operations on vehicle
        virtual ~vehicle() {}
        virtual void foo() const = 0 ;
        // virtual ..... = 0 ;

    protected:
        vehicle() {} // constructor for normal objects

        struct for_exemplars {};
        vehicle( for_exemplars ) ; // constructor for prototype objects

    private:
        virtual pointer create( const std::string& id ) const = 0 ;
};


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/////////////  implementation file vehicle.cc //////////////////
// #includes

namespace
{
    constexpr std::size_t MAX_DERIVED_CLASSES = 1024 ;
    vehicle* exemplars[MAX_DERIVED_CLASSES] ; // chain of responsibility
    std::size_t n_exemplars ;
}

vehicle::vehicle( for_exemplars ) // constructor for prototype objects
{ if( n_exemplars < MAX_DERIVED_CLASSES ) exemplars[ n_exemplars++ ] = this ; }

vehicle::pointer vehicle::create_vehicle( const std::string& id )
{
    vehicle::pointer p { nullptr } ;
    for( std::size_t i = 0 ; i < n_exemplars ; ++i )
        if( ( p = exemplars[i]->create(id) ) ) break ;
    return pointer(p) ;
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/////////////  header file exemplar_helper.h //////////////////
// #includes

template < typename DERIVED, const char* ID > struct exemplar_helper : vehicle
{
    static const DERIVED exemplar ;

    using vehicle::vehicle ; // inherited constructor

    private: virtual pointer create( const std::string& id ) const override
    {
        if( id == ID ) return pointer{ new DERIVED() } ;
        else return pointer{ nullptr } ;
    }
};

template < typename DERIVED, const char* ID >
const DERIVED exemplar_helper<DERIVED,ID>::exemplar{ for_exemplars() } ;
/*
Note: We are relying on:

There can be more than one definition of a ... static data member
of a class template ... in a program provided that each definition
appears in a different translation unit, and provided the definitions satisfy the
following requirements.  Given such an entity named D defined in more than one
translation unit,

.... requirements elided; they are given in section 3.2 (ODR) of the IS

If the definitions of D satisfy all these requirements, then the program shall
behave as if there were a single definition of D.
*/

// couple of helper macros to make things easy for derived class authors
// who do may not want to understand what is going on behind the scenes

#define BEGIN_DEFINE_VEHICLE( CLASS_NAME ) \
\
extern const char id__##CLASS_NAME[] = #CLASS_NAME ; \
\
struct CLASS_NAME : exemplar_helper< CLASS_NAME, id__##CLASS_NAME > \
{\
    using base = exemplar_helper< CLASS_NAME, id__##CLASS_NAME > ; \
    explicit CLASS_NAME( for_exemplars fe ) : base(fe) {} \

#define END_DEFINE_VEHICLE(CLASS_NAME) \
} ; \
\
template class exemplar_helper< CLASS_NAME, id__##CLASS_NAME > ;
/* Note: this forces instantiation of every member of the class
         we need to ensure that the exemplar object is instantiated */


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/////////////  typical component trying to instantiate vehicle objects //////////////////
// #includes

int main()
{
    vehicle::pointer v[] {
                           vehicle::create_vehicle("Car"),
                           vehicle::create_vehicle("Hovercraft"),
                           vehicle::create_vehicle("Ship"),
                           vehicle::create_vehicle("invalid_tag")
                         } ;
    for( auto p : v )
    {
        if(p) p->foo() ;
        else std::cout << "vehicle object could not be instantiated\n" ;
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// derived classes of vehicle Car, Hovercraft etc. do not need header files
// the classes themselves are not directly accessed by the rest of the program
// they are instantiated via the exemplar / chain of responsibility mechanism
// and accessed via the base class interface


/////////////  implementation file car.cc //////////////////
// #includes

BEGIN_DEFINE_VEHICLE(Car)

    Car() { /* .... */ }

    virtual void foo() const override { std::cout << "Car::foo\n" ; }

    // other overrides

    // implementation

END_DEFINE_VEHICLE(Car)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
/////////////  implementation file hovercraft.cc //////////////////
// #includes

BEGIN_DEFINE_VEHICLE(Hovercraft)

    Hovercraft() { /* .... */ }

    virtual void foo() const override { std::cout << "Hovercraft::foo\n" ; }

    // other overrides

    // implementation

END_DEFINE_VEHICLE(Hovercraft)


1
2
3
4
5
6
7
8
9
10
11
12
13
14
/////////////  implementation file ship.cc //////////////////
// #includes

BEGIN_DEFINE_VEHICLE(Ship)

    Ship() { /* .... */ }

    virtual void foo() const override { std::cout << "Ship::foo\n" ; }

    // other overrides

    // implementation

END_DEFINE_VEHICLE(Ship)

http://coliru.stacked-crooked.com/a/5d210758b2813d83
Hi JLBorges,

Thank you very much for your reply. I tried and tested this, it works like a charm. I love the solution as well. I never used macros before. I'll definitely be making use of macros if and probably when we switch over to C++.

Thanks again for the solution.
Topic archived. No new replies allowed.