Here’s another interesting C++ problem I encountered today. As is often the case, I found my answer by asking over at Stack Overflow. The question can be found here: http://stackoverflow.com/questions/1260954/how-can-i-keep-track-of-enumerate-all-classes-that-implement-an-interface
The issue was that I was writing a simulation program where I knew I would eventually want to simulate multiple different vehicles, with multiple different controllers, and multiple different estimators. Naturally, this led me to define an interface for each of these things, but the problem was that I only really want to implement a few subclasses of these interfaces now. In particular, I only have two vehicles, 2 controllers, and 1 estimator I’m interested in completing now, but I will probably want to implement at least 2 more vehicles, 2 more controllers, and 2 or 3 more estimators. And, finally, as far as the simulation is designed, I would like for the user to be able to select from a list of choices which vehicle, which controller, and which estimator to use. Therefore, I was looking for a clean way to keep a kind of registry of classes that implement each of these interfaces so that I wouldn’t have to go back and change the interface code later when I implemented more sub-classes.
The solution that was accepted was to implement a registry class that maintains a mapping of class-names to constructors, and then update that mapping from within the class definition file from each of the implementing sub-classes using a static initializer. I went one step further and made the registry class generic (templated), and this is the result. There is an example code below.
File: CInterfaceRegistry.h
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | /** * file CInterfaceRegistry.h * date: Aug 11, 2009 * brief: * * detail: */ #ifndef CINTERFACEREGISTRY_H_ #define CINTERFACEREGISTRY_H_ #include <map> #include <string> #include <set> #include <typeinfo> #include "exception/IllegalArgumentException.h" namespace utility { /** * brief Generic singleton object to maintains a registry of subclasses * that implement a particular interface * * This clever solution was taken from the discussion at the following site: * http://stackoverflow.com/questions/1260954/how-can-i-enumerate-all- * classes-that-implement-an-interface * * note: The registry will allow the registration of any class that extends * an interface so long as it has a factory method, but the * RegisterWithInterface macro will only work with classes that have * named that method create() * * */ template <typename InterfaceType> class CInterfaceRegistry { private: std::map< std::string, InterfaceType* (*)(void) > m_creatorMap; /** * brief private to ensure that only the singleton ( the single * static instantiation ) of this template class is used */ CInterfaceRegistry(){} public: /** * brief returns the registry instance particular to the specified * interface (specified as template parameter) */ static CInterfaceRegistry& getInstance(); /** * brief registers a new subclass of a an interface */ bool registerClass( const std::string& name, InterfaceType* (*creator)(void) ); /** * brief registers a new subclass from it's typeid() result, rather * than from a hand-typed class name */ bool registerClass( const std::type_info& classType, InterfaceType* (*creator)(void) ); /** * brief returns a list of classes registered with this interface */ std::set<std::string> getClassNames(); /** * brief returns a new object of the specified class */ InterfaceType* createObjectOf( std::string className ); }; // A convient macro to compact the registration of a class #define RegisterWithInterface( CLASS, INTERFACE ) namespace { bool dummy_ ## CLASS = CInterfaceRegistry<INTERFACE>::getInstance().registerClass( typeid(CLASS), CLASS::create ); } /** * The use of this method ensures that this template class remains singleton. * Only the static registry object created by this method will exist for any * instantiation of this class. */ template <typename InterfaceType > CInterfaceRegistry<InterfaceType>& CInterfaceRegistry<InterfaceType>::getInstance() { static CInterfaceRegistry<InterfaceType> registry; return registry; } /** * To register a class with this registry, we map a factory function that is * capable of generating objects of the class and returning a pointer of the * interface type. The key we map it to is the name of the class. */ template <typename InterfaceType > bool CInterfaceRegistry<InterfaceType>::registerClass( const std::string& name, InterfaceType* (*creator)(void) ) { m_creatorMap[name] = creator; return true; } /** * For added convenience, we can avoid typing the class names by hand if we * use the typeid() operator. This method will extract the class name from * a type_info class and pass it on to the actual registration method. */ template <typename InterfaceType > bool CInterfaceRegistry<InterfaceType>::registerClass( const std::type_info& classType, InterfaceType* (*creator)(void) ) { return registerClass( std::string(classType.name()), creator ); } /** * To generate a list of the class names currently registered with this * interface, we iterate through the map and extract all the keys. */ template <typename InterfaceType > std::set<std::string> CInterfaceRegistry<InterfaceType>::getClassNames() { std::set<std::string> keys; typename std::map< std::string, InterfaceType* (*)(void) >::iterator pair; for( pair = m_creatorMap.begin(); pair != m_creatorMap.end(); pair++) keys.insert( pair->first ); return keys; } /** * To create a new object of the specified class, we simply de-reference the * stored factory method and execute it. */ template <typename InterfaceType > InterfaceType* CInterfaceRegistry<InterfaceType>::createObjectOf( std::string className ) { InterfaceType* (*creator)(void) = m_creatorMap[className]; if(creator) return *creator(); else throw IllegalArgumentException( className + "is not registered with the registry"); } } #endif /* CINTERFACEREGISTRY_H_ */ |
File: CInterfaceRegistryTest.cpp
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | /** * file CInterfaceRegistryTest.cpp * date: Aug 11, 2009 * brief: * * detail: */ #include "utility/CInterfaceRegistry.h" #include <iostream> #include <map> #include <set> using namespace utility; using std::map; using std::set; using std::cout; using std::string; // create the interface that we will be extending class IDummyInterface{}; // create the first of several implementations of that interface class CDerivedA : public IDummyInterface { public: // the class must have some kind of static factory method static IDummyInterface* create(){ return new CDerivedA(); } }; // and this is how we register the class with the registry bool dummyA = CInterfaceRegistry<IDummyInterface>::getInstance().registerClass( "CDerivedA", CDerivedA::create ); // we create, here, the second of several implementations, it's basically the // same as the first class CDerivedB : public IDummyInterface { public: // again with the static factory method static IDummyInterface* create(){ return new CDerivedB(); } }; // this is the same as above bool dummyB = CInterfaceRegistry<IDummyInterface>::getInstance().registerClass( "CDerivedB", CDerivedB::create ); // and a nother implementation class CDerivedC : public IDummyInterface { public: // ditto... static IDummyInterface* create(){ return new CDerivedC(); } }; // this time we use that convenient macro that does things a little more // compactly and, I think, without sacrificing readabilty RegisterWithInterface( CDerivedC, IDummyInterface ); int main() { // here we can retrieve a list of all the registered classes by // querying the registry object set<string> classes = CInterfaceRegistry<IDummyInterface>::getInstance().getClassNames(); cout << "Currently registered subclasses of IDummyInterface: n"; cout << "-- --------------------nn"; set<string>::iterator str; for( str = classes.begin(); str != classes.end(); str++ ) cout << *str << "n"; cout << "nndonen"; return 0; } |
Note that the name returned by querying typeid()
has a “9” inserted in the front of it. The names used to identify a type in the type_info
class are implementation specific, so it may or may not be a good idea to use them. In my case it will be fine.
Edit:
A better choice for the macro is to use something like this
1 2 3 4 5 6 7 8 | // A convient macro to compact the registration of a class #define RegisterWithInterface( CLASS, INTERFACE ) namespace { bool dummy_ ## CLASS = Registry<INTERFACE>::getInstance().registerClass( #CLASS, CLASS::createNew ); } |
which uses the class’s name from the source code instead of the type_info
name.