6

I'm wondering if it's possible to create a map of pointers of inherited classes. Here's an example of what I'm trying to do:

#include <string>
#include <map>

using namespace std;

class BaseClass
{
    string s;
};

class Derived1 : public BaseClass
{
    int i;
};

class Derived2 : public Derived1
{
    float f;
};

// Here's what I was trying, but isn't working
template<class myClass>
map<string, myClass>m;

int main()
{
    // Add BaseClasses, Derived1's, and/or Derived2's to m here
    return 0;
}

The errors I get are:

main.cpp(23): error C2133: 'm' : unknown size
main.cpp(23): error C2998: 'std::map<std::string,myClass>m' : cannot be a template definition

I get why I'm getting this error, but I'm wondering if it's possible to create a map that can hold different levels of inherited classes? If not, is it possible to create some sort of management system that can hold various class types? Or would I have to make different maps/vectors/arrays/etc. for each type of class?

Jon Purdy
  • 53,300
  • 8
  • 96
  • 166
JJandDjango
  • 61
  • 1
  • 2
  • 1
    Was there a reason you aren't just storing `BaseClass*`s? – Seth Carnegie Dec 25 '11 at 01:24
  • Is it possible to have a map of `BaseClass*`s, and add `Derived1*`s and `Derived2*`s to it while maintaining their functionality? I remember trying something like that earlier, but a map of `BaseClass*`s looked like it was converting whatever I to it into a `BaseClass*`, regardless of what it originally was. – JJandDjango Dec 25 '11 at 01:29
  • A pointer to a derived class can be implicitly converted to a pointer to a base class (so yes) but you have to have a way of figuring out what derived type the pointer points to an instance of, then you have to cast the base pointer to the derived pointer to use any derived functions. – Seth Carnegie Dec 25 '11 at 01:31
  • @user1114983, you'll only be able to use the methods declared in `BaseClass` (without resorting to casts), but other than that there is no lost functionality. – zneak Dec 25 '11 at 01:32

4 Answers4

5

Yes you can store inherited classes in map, but pointers to them, not objects themselves. Here's a short example (it lacks memory management on pointers)

#include <iostream>
#include <string>
#include <map>
#include <utility>

using namespace std;

class BaseClass
{
    string s;
public: 
    BaseClass() { s = "BaseClass";} 
    virtual void print() 
    {
        cout << s << std::endl;
    }
};

class Derived1 : public BaseClass
{
    int i;
public:
    Derived1() { i = 10; }
    void print() 
    {
        cout << i << std::endl;
    }

};

class Derived2 : public Derived1
{
    float f;
public:
    Derived2() { f = 4.3;}
    void print() 
    {
        cout << f << std::endl;
    }
};

int main()
{
    map<string, BaseClass*>m;
    m.insert(make_pair("base", new BaseClass()));
    m.insert(make_pair("d1", new Derived1()));
    m.insert(make_pair("d2", new Derived2()));
    m["base"]->print();
    m["d1"]->print();
    m["d2"]->print();

    return 0;
}
Anton
  • 4,395
  • 7
  • 32
  • 46
3

First things first:

template<class myClas>
map<string, myClass> m;

This is not valid C++ and could only mean something like a template alias, but I believe, that is not what you are looking for.

Storing polymorphic objects in C++ is complicated by slicing (constructing a value of the base type from a value of a derived type). Dynamic polymorphism can only be handled through references or pointers. You could potentially use std::ref or boost::ref for situations in which the map will only be passed down the callstack, but this requires some care. Often, storing pointers to the base is the way to go: std::map<std::string, base*>. Managing deallocation yourself is rather tedious and either std::map<std::string, std::unique_ptr> or std::map<std::string, std::shared_ptr> are preferred, depending if you need shared semantics or not.

Basic example. Someone should replace this with something more meaningful.

#include <memory>
#include <string>
#include <map>
#include <iostream>

class animal
{
public:
  virtual ~animal() {};
  virtual void make_sound() const = 0;
};

class dog : public animal
{
public:
  void make_sound() const { std::cout << "bark" << std::endl; }
};
class bird : public animal
{
public:
  void make_sound() const { std::cout << "chirp" << std::endl; }
};

int main()
{
  std::map<std::string, std::unique_ptr<animal>> m;
  m.insert(std::make_pair("stupid_dog_name", new dog));
  m.insert(std::make_pair("stupid_bird_name", new bird));
  m["stupid_dog_name"]->make_sound();
  return 0;
}
pmr
  • 58,701
  • 10
  • 113
  • 156
  • Your example is good, except you are using the std::unique_ptr which is part of C++11 and it's not that widely used at the moment. I wouldn't recommend the new standard features to a total newbie in the C++ world. – Anton Dec 25 '11 at 03:27
  • @Anton I see things the other way around. Every major implementation supports `unique_ptr` by now and there is no good reason why he should ever learn the old ways of doing this. Besides maintenance where he sees such code and replaces it with the modern idiom. – pmr Dec 25 '11 at 03:34
  • Well, in a perfect world everyone would use the most recent version of a technology. But the truth is - there are too many cases in real life when you are limited by your company rules, or by some other means. It's not granted that when you start working in a company they will give you a fresh VisualStudio or up-to-date gcc compiler. And in this situation it's a real pain to downgrade your knowledge to the existing environment. Of course this doesn't apply when you write code for yourself or some open-source project – Anton Dec 25 '11 at 03:47
  • @Anton Your view has merit but there are various ways to object to it. It is moot to discuss this here. Just add an example of freeing the memory to your answer and he knowns everything he needs. – pmr Dec 25 '11 at 03:53
  • I know this is ancient, but here I am. Is it possible to do this with references instead of pointers? I'm just curious really. I'm wondering if references would make the memory management easier or faster. – GaryO Jan 12 '18 at 23:19
1

Below is the expansion of solution suggested by anton.

#include <iostream>
#include <string>
#include <map>
#include <utility>

using namespace std;

class BaseClass
{
    string s;

public: 
    BaseClass() { s = "BaseClass";} 
virtual ~ BaseClass(){}
virtual void print()=0;
};


class Derived1 : public BaseClass
{
    int i;
public:
    Derived1() { i = 10; }
    void print() 
    {
        cout << i << std::endl;
    }

};

class Derived2 : public Derived1
{
    float f;
public:
    Derived2() { f = 4.3;}
    void print() 
    {
        cout << f << std::endl;
    }
};

class factory
{
map<string, BaseClass*>m;
BaseClass* obj;
public:
factory()
{
obj=NULL;
}
BaseClass* FindType(string s);
void AddType(string s,BaseClass *obj);
void deleter();
~factory(){cout<<"deleting objects from map"<<endl;
deleter();
}
};
void factory :: AddType(string s,BaseClass* obj)
{ 
 m.insert(make_pair(s,obj ));
}
void factory ::deleter ()
 {

   for (auto  pObj = m.begin( );
        pObj != m.end( ); ++pObj) {

      delete pObj->second;
   }

   m.clear( );  

 }
BaseClass* factory::FindType(string s)
{
if(m.find(s)!=m.end())
{

return  m[s];
}

return NULL;
}

int main()
{   
   BaseClass* obj;
   factory fact_obj; 
   fact_obj.AddType("d1",new Derived1());
   fact_obj.AddType("d2",new Derived2());
   obj=fact_obj.FindType("d1");
   if(obj!=NULL)
      {
    obj->print();
      }
    obj=fact_obj.FindType("d2");
    if(obj!=NULL)
     {
     obj->print(); 
     }

    return 0;
}
user2997518
  • 812
  • 6
  • 17
1

You may have template on classes and functions, but not on instances.

You should stick to the map to BaseClass*'es.

Krizz
  • 11,362
  • 1
  • 30
  • 43