How can I access the members of a subclass from a superclass with a different constructor?

148 views Asked by At

I have the following class and typedef:

class Object

{
protected:
    long int id;
public:
    Object(void);
    ~Object(void) {};
    long int get_id(void);
};

typedef map<string, Object> obj_map;

And then I have its child:

class Image: public Object

{
private:
    path full_path;
    int x;
    int y;
    img image;
public:
    Image(path p, int x_pos = 0, int y_pos = 0);
    ~Image(void) {};

    void draw(int flags = 0);
    void move_north(float velocity);
    void move_south(float velocity);
    void move_west(float velocity);
    void move_east(float velocity);

    path get_path(void);
    img get_image(void);
    int get_x(void);
    int get_y(void);
    void set_path(path p);
    void set_image(img _image);
    void set_x(int value);
    void set_y(int value);
};

Every object is stored in this static map obj_map, inside a class called App. So let us suppose we have an object under the key foo. We can print its id this way:

cout << App::objects.find("foo")->second.get_id() << endl;

Now, let us suppose I've passed an Image object as a value to this map. When I try to access it, I can access the id normally. But, when I try to access an Image method:

cout << App::objects.find("foo")->second.get_x() << endl;

I get a:

error: 'class Object' has no member named 'get_x'

And that's because the class Object in fact does not have it. So, I try to cast it:

cout << static_cast<Image>(App::objects.find("foo")->second).get_x() << endl;

And I get it:

error: no matching function for call to 'Image::Image(Object&)'

And if I try a dynamic_cast, I get something ghastly...

error: cannot dynamic_cast 'App::objects.std::map<_Key, _Tp, _Compare, _Alloc>::find<std::basic_string<char>, Object, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, Object> > >((* & std::basic_string<char>(((const char*)"foo"), (*(const std::allocator<char>*)(& std::allocator<char>()))))).std::_Rb_tree_iterator<_Tp>::operator-><std::pair<const std::basic_string<char>, Object> >()->std::pair<const std::basic_string<char>, Object>::second' (of type 'class Object'

I don't get it. The constructor is not inherited. The constructor has nothing to do with it. An Image object has a completely different constructor, but IT IS an Object. And the point is: I can't create a map of Images because eventually I'll have many Objects in my engine, and I want to be able to access them from the superclass.

2

There are 2 answers

0
M.M On BEST ANSWER

This map:

typedef map<string, Object> obj_map;

only stores Object objects. When you try to put an Image in, it is sliced down and you lose everything in the Image that was not actually part of Object.

The behaviour that you seem to be looking for is called polymorphism. To activate this you must do two things:

  • Make Object polymorphic
  • Store pointers to Objects in the map

The first part is simple: add virtual before ~Object(void).

For the second part, there are three out-of-the-box ways to do this:

map<string, Object *>              // 1
map<string, unique_ptr<Object>>    // 2
map<string, shared_ptr<Object>>    // 3

The difference between these options is in how you want to manage the lifetime of those objects.

If the map should own the object use (2) or (3). If a single object may occur in multiple places in the map, or you want to be able to copy the map, or there may be references outside the map referring to the same object, use (3); otherwise use (2).

If you use (1) you must take care that objects are correctly deleted after their last use in the map.

You may need #include <memory> to use the smart pointer classes.

Inserting an object would look like, respectively:

map["foo"] = new Image(....);
map["foo"] = make_unique<Image>(....);
map["foo"] = make_shared<Image>(....);

You can actually use assignment from new, or .reset() for the last two, but the make_ functions are the preferred style.

0
sirgeorge On

You should store pointers to objects in your map - not the objects (preferably shared_ptr) :

typedef map<string, shared_ptr<Object>> obj_map;
obj_map my_map;
path some_path; // I have no idea what 'path' is and how to use it or even create it
my_map.insert(make_pair("image1",make_shared<Image>(some_path, 0,0)));
my_map.insert(make_pair("image2",make_shared<Image>(some_path, 5,5)));
my_map.insert(make_pair("image3",make_shared<Image>(some_path, 10,10)));
auto iter = my_map.find("image3");
auto pImg = dynamic_pointer_cast<Image>(iter->second);
cout << pImg->get_x() << endl; //this should display '10'

When you put Image into map defined as map<string, Object> obj_map you are truncating your Image object to Object (base class). In other words your map would always store only and only Object elements (not Images). If you store pointers - you store objects of the actual class (your just store them as pointers to the base class). Because nothing is truncated - you can always cast the pointer to the derived class and access its methods.

I hope that helps.