Instantiate classe using a map

I have a long list of classes and I want to instantiate one according to a string.
I currently have code (that works) like the following:
BaseClass* InstantiateClass(const string& key){
if(key == "SpecificClass1"){
SpecificClass1* p = new SpecificClass1();
return p;
}
else if(key == "SpecificClass2"){
SpecificClass2* p = new SpecificClass2();
return p;
}
...
else if(key == "SpecificClass999"){
SpecificClass999* p = new SpecificClass999();
return p;
}
else{
throw("Unknown class");
}
}

I would like to replace that using a map. Something like the following code (which doesn't work):
BaseClass* InstantiateClass(const string& key){
map<string, BaseClass*> MapOfClasses;
MapOfClasses["SpecificClass1"] = SpecificClass1; // Compile error
MapOfClasses["SpecificClass2"] = SpecificClass2;
...
MapOfClasses["SpecificClass999"] = SpecificClass999;

(MapOfClasses[key])* p = new MapOfClasses[key]();// Most likely wrong
return 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
# include <functional>
# include <map>
# include <memory>
# include <string>
# include <typeinfo>
# include <iostream>

struct BaseClass { virtual ~BaseClass() = default; };
struct SpecificClass1: BaseClass {};
struct SpecificClass2: BaseClass {}; 

using key_type = std::string;
std::shared_ptr<BaseClass> make_base_class(key_type const& key) {
  using make_fn = std::function<std::shared_ptr<BaseClass>()>;
  static const std::map<key_type, make_fn> dispatch_table
  { { "SpecificClass1", []{ return std::make_shared<SpecificClass1>(); } },
    { "SpecificClass2", []{ return std::make_shared<SpecificClass2>(); } } }; 

  return dispatch_table.at(key)();
}

int main() {
  std::cout << typeid(*make_base_class("SpecificClass1")).name() << '\n';
  std::cout << typeid(*make_base_class("SpecificClass2")).name() << '\n';
            
  try { 
    std::cout << typeid(*make_base_class("UnknownClass42")).name() << '\n';
  } catch (std::exception&) { std::cerr << "unknown type.\n"; }
}


Live demo:
http://coliru.stacked-crooked.com/a/4e572bf091ba7c37
> I have a long list of classes and I want to instantiate one according to a string
> I currently have code (that works) like the following:
> I would like to replace that using a map.

Using an exemplar (aka prototype) pattern would allow us to encapsulate the knowledge about
derived classes. https://en.wikipedia.org/wiki/Prototype_pattern

That is to say, ideally:
a. Adding a new derived class should require nothing more than adding the code for that class
(should not require breaking any code elsewhere in the program).
b. The determination of whether a string is the right string to instantiate an object should be localised to the appropriate derived class implementation (and the logic for this could be more involved than just looking up a string in a map: for instance, instantiate the object if the string matches a regular expression.)

Here's a sketch of how this could be achieved:

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
#include <string>
#include <memory>

// base.h
struct base
{
    using pointer = std::unique_ptr<base> ;
    virtual ~base() = default ;
    virtual std::string name() const = 0 ;

    static pointer make( const std::string& key ) ;

    protected:
        base() = default ; // for normal objects
        struct prototype{}; base(prototype) ; // for exemplars

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

// base.cpp
// #include "base.h"
#include <array>
namespace
{
    constexpr std::size_t MAX_DERIVED_CLASSES = 128 ;
    // simple array for brevity: elides order of initialisation concerns
    static const base* exemplars[MAX_DERIVED_CLASSES] {} ;
    static std::size_t num_exemplars = 0 ;
}

base::base(base::prototype)
{
    // if( num_exemplars > MAX_DERIVED_CLASSES ) throw ...
    exemplars[ num_exemplars++ ] = this ;
}

base::pointer base::make( const std::string& key )
{
    // chain of responsibility:
    // https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern
    for( std::size_t i = 0 ; i < num_exemplars ; ++i )
        if( base::pointer p = exemplars[i]->create(key) ) return p ;

    return nullptr ; // invalid key (did not create an object)
}

// derived_A.cpp (need not have a header)
// #include "base.h"
#include <cctype>

namespace
{
    struct derived_A : base
    {
        derived_A( /* ... */ ) : id(++slno) {}

        virtual std::string name() const override
        { return "derived_A #" + std::to_string(id) ; }

        private:
            derived_A( base::prototype proto ) : base(proto) {}

            unsigned int id ;

            // key is either "derived_A" or just "A" ignoring case
            static bool is_key( std::string candidate )
            {
                for( char& c : candidate ) c = std::tolower(c) ;
                return candidate == "derived_a" || candidate == "a" ;
            }

            virtual pointer create( const std::string& key ) const override
            { return is_key(key) ? std::make_unique<derived_A>( /* ... */ ) : nullptr ; }

            static const derived_A exemplar ;
            static unsigned int slno ;
    };

    const derived_A derived_A::exemplar{ base::prototype{} } ;
    unsigned int derived_A::slno = 0 ;
}

// derived_B.cpp (need not have a header)
// #include "base.h"

namespace
{
    struct derived_B : base
    {
        derived_B( std::string id ) : id(id) {}

        virtual std::string name() const override
        { return "derived_B{'" + id + "'}" ; }

        private:
            derived_B( base::prototype proto ) : base(proto) {}

            // key is: begins with upper case 'B'
            static bool is_key( const std::string& candidate )
            { return !candidate.empty() && candidate.front() == 'B' ; }

            std::string id ;

            virtual pointer create( const std::string& key ) const override
            { return is_key(key) ? std::make_unique<derived_B>(key) : nullptr ; }

            static const derived_B exemplar ;
    };

    const derived_B derived_B::exemplar{ base::prototype{} } ;
}

// main.cpp
#include <iostream>
// #include "base.h"

int main()
{
    for( std::string key : { "B101", "DERIVED_A", "xxxyyy", "a", "B767" } )
    {
         std::cout << "key: " << key ;
         auto p = base::make(key) ;
         if(p) std::cout << "  created object: " << p->name() << '\n' ;
         else std::cout << "  is an invalid key\n" ;
    }
}

http://coliru.stacked-crooked.com/a/10eb428dff0ba5b8
http://rextester.com/EFCKA22456
Topic archived. No new replies allowed.