Charlie
Charlie

Reputation: 33

Good way to create class object instance according to a string value in c++?

See the code snip for the question:

class Base; 
typedef Base* BasePtr; 

template<typename D>
BasePtr CreateDerivedClass() {
    return new D();
}

class D1 : public Base {
....
};

class D2 : public Base {
....
}

void main() {
    std::string derived_name = {"D1", "D2"}; 
    std::vector<BasePtr> derived_vec; 
    for (auto& name : derived_name) {
// QUESTION: Other than factory design pattern, how can I pass the derived 
// class typename which is the value of string "name" to CreateDerivedClass<name> ?
        derived_vec.push_back(CreateDerivedClass<name>()); 
    }
}

Note: Here, I don't want to use the factory design pattern which required the derived class register itself during definition.

Upvotes: 0

Views: 281

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 596352

Template parameter values must be known at compile-time. There is simple no way to directly convert a string at runtime to a type for use in a template parameter. You will have to compare the string value at runtime to decide which CreateDerivedClass specialization to call.

For instance, the simplest way to handle this is to use a std::map where its elements hold pointers to the specialized functions that you want to be able to call, eg:

using createFuncType = BasePtr (*)();
std::map<std::string, createFuncType> createFuncs;
createFuncs["D1"] = &CreateDerivedClass<D1>;
createFuncs["D2"] = &CreateDerivedClass<D2>;
...

Then you can do this:

std::string derived_names[] = {"D1", "D2"}; 
std::vector<BasePtr> derived_vec; 
for (auto& name : derived_names) {
    derived_vec.push_back(createFuncs[name]());
}

There is really no other option in this situation.


That being said, you should be using std::unique_ptr<Base> instead of raw Base* pointers, eg:

#include <map>
#include <vector>
#include <string>
#include <memory>

class Base {
public:
    virtual ~Base() = default;
};

using BasePtr = std::unique_ptr<Base>;
using createFuncType = BasePtr (*)();

template<typename D>
BasePtr CreateDerivedClass() {
    return std::make_unique<D>();
}

class D1 : public Base {
    ...
};

class D2 : public Base {
    ...
}

...

int main() {
    std::map<std::string, createFuncType> createFuncs;
    createFuncs["D1"] = &CreateDerivedClass<D1>;
    createFuncs["D2"] = &CreateDerivedClass<D2>;
    ...

    std::string derived_names = {"D1", "D2", ...};
    std::vector<BasePtr> derived_vec; 
    for (auto& name : derived_names) {
        derived_vec.push_back(createFuncs[name]()); 
    }

    ...

    return 0;
}

Upvotes: 3

Aviv Nachman
Aviv Nachman

Reputation: 49

The type T of the template should be known in compilation time, this is because the compiler needs to create the piece of code matching to the implementation of the class/function with the type T. This is how templates in C++ work. What if derived_names will consist the value "D8" which is not a known type, we are going to have a trouble.

Upvotes: 0

Related Questions