Reputation: 3144
imagine I have a bunch of C++ related classes (all extending the same base class and providing the same constructor) that I declared in a common header file (which I include), and their implementations in some other files (which I compile and link statically as part of the build of my program).
I would like to be able to instantiate one of them passing the name, which is a parameter that has to be passed to my program (either as command line or as a compilation macro).
The only possible solution I see is to use a macro:
#ifndef CLASS_NAME
#define CLASS_NAME MyDefaultClassToUse
#endif
BaseClass* o = new CLASS_NAME(param1, param2, ..);
Is it the only valuable approach?
Upvotes: 30
Views: 31242
Reputation: 73
Though the question exist now for more than four years it is still useful. Because calling for new code unknown at the moment of compiling and linking the main code files is in these days a very common scenario. One solution to this question isn't mentioned at all. Thus, I like to point the audience to a different kind of solution not built in C++. C++ itself has no capability to behave like Class.forName()
known from Java or like Activator.CreateInstance(type)
known from .NET. Due to mentioned reasons, that there is no supervision by a VM to JIT code on the fly. But anyhow, LLVM, the low level virtual machine, gives you the needed tools and libs to read-in a compiled lib. Basically, you need to execute two steps:
clang -emit-llvm -o foo.bc -c foo.c
ParseIRFile()
method from llvm/IRReader/IRReader.h
to parse the foo.bc
file to get the relevant functions (LLVM itself only knows functions as the bitcode is a direct abstraction of CPU opcodes and quite unsimiliar to more high-level intermediate representations like the Java bytecode). Refer for instance to this article for a more complete code description.After setting up these steps sketched above you can call dynamically also from C++ other prior unknown functions and methods.
Upvotes: 1
Reputation: 41509
A way to implement this is hard-coding a mapping from class 'names' to a factory function. Templates may make the code shorter. The STL may make the coding easier.
#include "BaseObject.h"
#include "CommonClasses.h"
template< typename T > BaseObject* fCreate( int param1, bool param2 ) {
return new T( param1, param2 );
}
typedef BaseObject* (*tConstructor)( int param1, bool param2 );
struct Mapping { string classname; tConstructor constructor;
pair<string,tConstructor> makepair()const {
return make_pair( classname, constructor );
}
} mapping[] =
{ { "class1", &fCreate<Class1> }
, { "class2", &fCreate<Class2> }
// , ...
};
map< string, constructor > constructors;
transform( mapping, mapping+_countof(mapping),
inserter( constructors, constructors.begin() ),
mem_fun_ref( &Mapping::makepair ) );
EDIT -- upon general request :) a little rework to make things look smoother (credits to Stone Free who didn't probably want to add an answer himself)
typedef BaseObject* (*tConstructor)( int param1, bool param2 );
struct Mapping {
string classname;
tConstructor constructor;
operator pair<string,tConstructor> () const {
return make_pair( classname, constructor );
}
} mapping[] =
{ { "class1", &fCreate<Class1> }
, { "class2", &fCreate<Class2> }
// , ...
};
static const map< string, constructor > constructors(
begin(mapping), end(mapping) ); // added a flavor of C++0x, too.
Upvotes: 10
Reputation:
In the past, I've implemented the Factory pattern in such a way that classes can self-register at runtime without the factory itself having to know specifically about them. The key is to use a non-standard compiler feature called (IIRC) "attachment by initialisation", wherein you declare a dummy static variable in the implementation file for each class (e.g. a bool), and initialise it with a call to the registration routine.
In this scheme, each class has to #include the header containing its factory, but the factory knows about nothing except the interface class. You can literally add or remove implementation classes from your build, and recompile with no code changes.
The catch is that only some compilers support attachment by initialisation - IIRC others initialise file-scope variables on first use (the same way function-local statics work), which is no help here since the dummy variable is never accessed and the factory map will always be found empty.
The compilers I'm interested in (MSVC and GCC) do support this, though, so it's not a problem for me. You'll have to decide for yourself whether this solution suits you.
Upvotes: 0
Reputation: 135255
This is a problem which is commonly solved using the Registry Pattern:
This is the situation that the Registry Pattern describes:
Objects need to contact another object, knowing only the object’s name or the name of the service it provides, but not how to contact it. Provide a service that takes the name of an object, service or role and returns a remote proxy that encapsulates the knowledge of how to contact the named object.
It’s the same basic publish/find model that forms the basis of a Service Oriented Architecture (SOA) and for the services layer in OSGi.
You implement a registry normally using a singleton object, the singleton object is informed at compile time or at startup time the names of the objects, and the way to construct them. Then you can use it to create the object on demand.
For example:
template<class T>
class Registry
{
typedef boost::function0<T *> Creator;
typedef std::map<std::string, Creator> Creators;
Creators _creators;
public:
void register(const std::string &className, const Creator &creator);
T *create(const std::string &className);
}
You register the names of the objects and the creation functions like so:
Registry<I> registry;
registry.register("MyClass", &MyClass::Creator);
std::auto_ptr<T> myT(registry.create("MyClass"));
We might then simplify this with clever macros to enable it to be done at compile time. ATL uses the Registry Pattern for CoClasses which can be created at runtime by name - the registration is as simple as using something like the following code:
OBJECT_ENTRY_AUTO(someClassID, SomeClassName);
This macro is placed in your header file somewhere, magic causes it to be registered with the singleton at the time the COM server is started.
Upvotes: 42
Reputation: 78914
You mention two possibilities - Command line and compilation macro but the solution for the each one is vastly different.
If the choice is made by a compilation macro than it's a simple problem which can be solved with #defines and #ifdefs and the like. The solution you propose is as good as any.
But if the choice is made in run-time using a command line argument then you need to have some Factory framework that is able to receive a string and create the appropriate object. This can be done using a simple, static if().. else if()... else if()...
chain that has all the possibilities or can be a fully dynamic framework where objects register and are being cloned to provide new instances of themselves.
Upvotes: 1
Reputation: 1253
Why not use an object factory?
In its simplest form:
BaseClass* myFactory(std::string const& classname, params...)
{
if(classname == "Class1"){
return new Class1(params...);
}else if(...){
return new ...;
}else{
//Throw or return null
}
return NULL;
}
Upvotes: 8
Reputation: 41096
In C++, this decision must be made at compile time.
During compile time you could use a typedef rather than a macor:
typedef DefaultClass MyDefaultClassToUse;
this is equivalent and avoids a macro (macros bad ;-)).
If the decision is to be made during run time, you need to write your own code to support it. The simples solution is a function that tests the string and instantiates the respective class.
An extended version of that (allowing independent code sections to register their classes) would be a map<name, factory function pointer>
.
Upvotes: 2