Is there any way to achieve the functionality of below code without creating the mapping between strings and classes manually?
template<class base, typename T>
base* f(const std::string &type, T &c) {
if(type == "ClassA") return new ClassA(c);
else if(type == "ClassB") return new ClassB(c);
// many more else if...
return nullptr;
}
All classes looks something like this:
class ClassA: public BaseClass {
public:
std::string label="ClassA";
...
};
And we can use it as:
BaseClass *b = f<BaseClass>("ClassA", DifferentObject);
Each new class results in a new if else
line of code. Is there any way to automate this so f
function "updates" itself when new supported class is added? The solution must work for C 11.
CodePudding user response:
A possible macro:
#include <memory>
#include <string>
class BaseClass {};
class ClassA : public BaseClass {
public:
std::string label = "ClassA";
explicit ClassA(int /*unused*/) {}
};
class ClassB : public BaseClass {
public:
std::string label = "ClassB";
explicit ClassB(int /*unused*/) {}
};
template<class base, typename T>
auto f(const std::string &type, T c) -> std::unique_ptr<base> {
#define CASE(NAME) \
if (type == "NAME") { \
return std::unique_ptr<base>(new NAME(c)); \
}
CASE(ClassA)
CASE(ClassB)
//...
#undef CASE
return nullptr; // Statement at the end needed for last else!
}
auto main() -> int {
auto b = f<BaseClass>("ClassA", 0);
}
Also use unique_ptr
since memory managing raw pointers are EVIL.
CodePudding user response:
In that case if the class name is equal to the string, you can simplify your code with the following macro:
#define STRING_TO_CLASS (className) if(type == "className") return new className(c);
template<class base, typename T>
base* f(const std::string &type, T &c) {
STRING_TO_CLASS(ClassA)
STRING_TO_CLASS(ClassB)
return nullptr;
}
Personally I hate macros, but it disturbs only me. However, at compile time, the following code wil be generated, after the macros are resolved.
template<class base, typename T>
base* f(const std::string &type, T &c) {
if(type == "ClassA") return new ClassA(c);
if(type == "ClassB") return new ClassB(c);
return nullptr;
}
As you see, in the end only the else
keyword is removed. Also, you need to modify your code if a new class is added.
CodePudding user response:
You could use the registry pattern like this:
#include <map>
#include <functional>
#include <string>
template< typename T, typename X >
using Factory = std::function< T* ( X& ) >;
template< typename Base, typename X >
struct Registry {
using Map = std::map<std::string,Factory<Base,X> >;
static Map registry;
template< typename T >
struct Register {
Register( const std::string& name ) {
registry[ name ] = []( X& x ) -> T* { return new T(x); };
}
};
};
template< typename Base, typename X >
Base* factory(const std::string &type, X &c ) {
auto it = Registry<Base,X>::registry.find( type );
if ( it!=Registry<Base,X>::registry.end() ) {
return (it->second)(c);
}
return nullptr;
}
struct X {};
struct A {
A( X& x ) {};
virtual ~A() {}
};
struct B : public A {
B( X& x ) : A(x) {};
};
struct C : public A {
C( X& x ) : A(x) {};
};
struct D : public B {
D( X& x ) : B(x) {};
};
// Register class
template<> Registry<A,X>::Map Registry<A,X>::registry{};
Registry<A,X>::Register<B> regB( "B" );
Registry<A,X>::Register<C> regC( "C" );
Registry<A,X>::Register<D> regD( "D" );
#include <iostream>
int main() {
X x;
A* ptr = factory<A,X>( "B", x );
B* bptr = dynamic_cast<B*>( ptr );
if ( bptr!= nullptr ) {
std::cout << "Success!" << std::endl;
return 0;
}
std::cout << "Failed!" << std::endl;
return 1;
}