How can I make unordered_map to contain more types of objects (rectangle, triangle...)

As title says, how can I make my unordered_map to contain more types of classes like: Triangle, rectangle, circle...

std::unordered_map<std::string, Object::Rectangle> objects;

class Object {

private:

std::string name;

public:

Object() = default;

Object(std::string name) : name(name) {}

const std::string* getName() {

return &name;
}

};

class Rectangle : public Object {

private:

sf::RectangleShape rectangleshape;

public:

Rectangle() = default;

Rectangle(std::string name, float width, float height) : rectangleshape(sf::RectangleShape(sf::Vector2f(width, height))), Object(name) {}

void setPosition(float position_x, float position_y) {

rectangleshape.setPosition(position_x, position_y);

}

void setAngle(float angle) {

rectangleshape.setRotation(angle);

}

sf::RectangleShape* getRectangleShape() {

return &rectangleshape;
}

Position getPosition() {

return Position(rectangleshape.getPosition().x, rectangleshape.getPosition().y);
}

};
Like so:

std::unordered_map<std::string, std::shared_ptr<Object> > objects;

Everything that inherits from Object can be stored in objects. Using std::shared_ptr means that the objects are destroyed as soon as they are not longer needed.
The container cannot store different types. Therefore, we do not put those objects into the container. We store pointers in the container. We can reach the actual objects via those pointers.

Public inheritance represents IS-A relationship. Rectangle is an Object. In ideal case the interface of Object is sufficient and we can use all Objects as Objects no matter what their actual shape is. There is dynamic_cast for accessing the interface of a derived type.


Note that:
1
2
3
4
5
6
7
8
container_type<Object> foo;
Circle bar;
foo.insert ( bar );

// effectively perfoms:
Object gaz;
Circle bar;
gaz = bar;

The gaz is an Object. The gaz is not a Circle. The assignment copies only the base class bits from Circle object into Object object. Loss of data. Slicing.

However:
1
2
3
Rectangle * pr = ...;
Object * po;
po = pr;

An address is an address. The po points to the same Rectangle object as the pr does.

When you have a list of pointers to Objects, the actual pointed to objects can be various derived types. Polymorphism.
But how can i get Rectange from shared_ptr?
Don't see a strong reason to mess around with pointers and dynamically allocated objects (in particular, objects with shared ownership semantics) in user code.

Since the type Object is not polymorphic, using boost::variant
http://www.boost.org/doc/libs/1_62_0/doc/html/variant/tutorial.html#variant.tutorial.basic
is an option; might as well get familiar with it now; (in a slightly more polished form) it will be part of C++17.
http://en.cppreference.com/w/cpp/utility/variant

Something like this, perhaps:
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
#include <iostream>
#include <string>
#include <unordered_map>
#include <boost/variant.hpp>
#include <iomanip>

struct object
{
    object( std::string name = {} ) : name(name) {} // converting constructor

    std::string get_name() const { return name ; }

    std::string name ;
};

struct rectangle : object { using object::object ; };
struct triangle : object { using object::object ; };
struct circle : object { using object::object ; };

int main()
{
    using shape = boost::variant<rectangle,triangle,circle> ; 
    std::unordered_map< std::string, shape > object_map ;

    rectangle rects[] = { {"one"}, {"seven"}, {"three"} } ;
    for( const auto& r : rects ) object_map.emplace( r.get_name(), r ) ;

    triangle trias[] = { {"two"}, {"six"}, {"four"} } ;
    for( const auto& t : trias ) object_map.emplace( t.get_name(), t ) ;

    circle circs[]  = { {"five"}, {"nine"}, {"five"} } ;
    for( const auto& c : circs ) object_map.emplace( c.get_name(), c ) ;

    std::cout << "object_map contains:\n-------------------\n" ;
    for( const auto& pair : object_map )
    {
        std::cout << std::setw(8) << std::quoted(pair.first) << "  " ;
        boost::apply_visitor( []( const auto& s ) { std::cout << typeid(s).name() << '\n' ; },
                              pair.second ) ;
    }
    std::cout << "-------------------\n" ;

    try
    {
       const rectangle& rect = boost::get<rectangle&>( object_map["seven"] ) ;
       std::cout << "got rectangle at object_map[\"seven\"]\n" ;

       const circle& circ = boost::get<circle&>( object_map["five"] ) ;
       std::cout << "got circle at object_map[\"five\"]\n" ;

       std::cout << "try to get circle at object_map[\"six\"]: " ;
       const circle& circ2 = boost::get<circle&>( object_map["six"] ) ; // throws: not a circle
       std::cout << "got circle at object_map[\"six\"]\n" ;
    }
    catch( const std::exception& ) { std::cerr << "get failed: incorrect type\n" ; }
}

http://coliru.stacked-crooked.com/a/2880c890cfe9b035
Thank you very much for your answer, it's really helpful. But is there another way to get type of the shape without using try and catch?
The simplest (and recommended) way is to use a visitor: a visitor with one overloaded call operator for each type in the variant.

For instance, a visitor to draw the shape would be something like this:
1
2
3
4
5
6
7
8
9
10
11
struct draw_it // visitor to draw a shape
{
    void operator() ( const rectangle& rect ) const
    { std::cout << "draw rectangle '" << rect.get_name() << "'\n" ; }

    void operator() ( const triangle& tri ) const
    { std::cout << "draw triangle '" << tri.get_name() << "'\n" ; }

    void operator() ( const circle& circ ) const
    { std::cout << "draw circle '" << circ.get_name() << "'\n" ; }
};


To query a boost::variant for the type of the object it contains, use the member function which()
http://www.boost.org/doc/libs/1_62_0/doc/html/boost/variant.html#idm46563152514272-bb
Then switch on the index returned by which() to retrieve the correct object without fear of exceptions.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
     switch( some_shape.which() )
     {
        case 0 : // this shape is a rectangle
        {
            const rectangle& rect = boost::get<rectangle>(some_shape) ;
            std::cout << "draw rectangle '" << rect.get_name() << "'\n" ;
            break ;
        }
        case 1 : // this shape is a triangle
        {
            const triangle& tri = boost::get<triangle>(some_shape) ;
            std::cout << "draw triangle '" << tri.get_name() << "'\n" ;
            break ;
        }
        case 2 : // this shape is a circle
        {
            const circle& circ = boost::get<circle>(some_shape) ;
            std::cout << "draw circle '" << circ.get_name() << "'\n" ;
            break ;
        }


Example: http://coliru.stacked-crooked.com/a/1ad9b2fcf46873b1
Thank you very much.
Topic archived. No new replies allowed.