Pregunta

I have a problem designing a class that will allow me to draw objects of various shapes.

  1. Shape is the base class
  2. Triangle, Square, Rectangle are derived classes from Shape class
  3. I have a vector<Shape*> ShapeCollection that stores the derived objects i.e. Triangle,Square, Rectangle
  4. Once I pick the an object from the vector I need to draw the object onto the screen.

At this point I am stuck at what the design of a class should be where as a single 'Drawing' class will do the drawing, consuming an object of 'Shape' class. As the vector will contain different objects of the same base class Shape. As I have a thread that picks up an object from the vector and once I have an object I must be able to draw it properly.

So more or less below is what I say

class Drawing
{
public:
   void Draw(Shape* shape, string objectName)
   {
       // Now draw the object.
       // But I need to know which Object I am drawing or use
       // switch statements to identify somehow which object I have
       // And then draw. I know this is very BAD!!!
       // e.g.
        switch(objectName)
        {
          case "rectangle":
                DrawRectangle((Rectangle*) shape)
          break;
          //Rest of cases follow
        }
   }
}

Where as I will have a DrawSquare, DrawTriangle function which will do the drawing.

This must be something that has been solved. There must be a better way of doing this as all this switch statement has to go away somehow!

Any guidance is much appreciated.

Thanks


@Adrian and @Jerry suggested to use virtual function, I thought of it, but I need to have my Drawing away from the base class Shape

¿Fue útil?

Solución

You would use polymorphism.

  1. Make a pure virtual function in your base class (i.e. when declaring the function assign it to 0 as in void DrawShape() = 0;)
  2. Declare and define that function in your derived classes.

That way you can just call DrawShape() on each of these objects even if it is passed as a Shape object.

Alternatives (NOTE: code has not been tested):

  1. Function pointer, which is like building your own vtable aka delegate.

    struct square
    {
        void (*draw)(square&);
    };
    
    void drawSquare(square& obj)
    {
      // draw square code
      // there is no 'this'. must access members via `obj`.
    }
    
    square s;
    s.draw = drawSquare;
    s.draw(s);
    
  2. Functor, which is a class that overrides operator() and also is like a delegate

    struct square
    {
        // Note that std::function can hold a function pointer as well as a functor.
        function<void(square&)> draw;
    };
    
    struct drawSquare
    {
        void oparator()(square& obj)
        {
            // draw square code
            // there is no 'this'. must access members via `obj`.
        }
    };
    
    square s;
    square s.draw = drawSquare();
    s.draw(s);
    

    NOTE: 1 and 2 can also be initialised with lambda functions:

    square s;
    s.draw = [](square& obj) {
      // draw square code
      // there is no 'this'. must access members via `obj`.
    };
    s.draw(s);
    

    NOTE: 1 could be done with a template:

    struct square;
    
    template <void (*DRAW)(square&)>
    struct square
    {
        void draw()
        {
            DRAW(*this);
        }
    };
    
    void drawSquare(square& obj)
    {
      // draw square code
      // there is no 'this'. must access members via `obj`.
    }
    
    square s<&drawSquare>;
    s.draw();
    

    NOTE: 2 could be done with a template as well:

    template <typename DRAW>
    struct square
    {
        void draw()
        {
            // First set of parentheses instantiate the DRAW object.
            // The second calls the functor.
            DRAW()(*this);
        }
    };
    
    struct drawSquare
    {
        void oparator()(square& obj)
        {
            // draw square code
            // there is no 'this'. must access members via `obj`.
        }
    };
    
    square s<drawSquare>;
    s.draw();
    

    Or alternatively, which would allow the passing of a stateful functor:

    template <typename DRAW>
    struct square
    {
        DRAW draw;
    };
    
    struct drawSquare
    {
        void operator()(square& obj)
        {
            // draw square code
            // there is no 'this'. must access members via `obj`.
        }
    };
    
    square s<drawSquare>;
    s.draw = drawSquare();
    s.draw(s);
    
  3. Inherit from another class that implements the function you want either with a templated base class (IIRC, this was done in the ATL). This is just rolling your own hard-coded vtable and is called the Curiously Recurring Type Pattern (CRTP).

    template <class D>
    struct shape
    {
       inline void draw() { return static_cast<D&>(*this).draw(); }
    };
    
    void draw(square& obj)
    {
        // draw square code
        // No 'this' available. must access shape members via `obj`.
    }
    
    struct square : public D<square>
    {
          void draw()
          {
              drawSquare(*this);
          }
    };
    

    Other examples can be found here and here.

  4. Have your draw class inherit from the type of shape class which inherits from the base shape class.

    struct shape
    {
         virtual void draw() = 0;
    };
    
    struct square : public shape
    {
    };
    
    struct drawSquare : public square
    {
         virtual void draw()
         {
             // draw square code
             // you access the square's public or protected members from here
         }
    };
    
  5. Use a std::unordered_map

    #include <unordered_map>
    #include <typeinfo>
    #include <functional>
    
    struct shape { };
    
    struct square : public shape { };
    
    void drawSquare(shape& o)
    {
         // this will throw an exception if dynamic cast fails, but should
         // never fail if called from function void draw(shape& obj).
         square& obj = dynamic_cast<square&>(o);
    
         // draw square code
         // must access shape members via `obj`.
    }
    
    std::unordered_map<size_t, std::function<void(shape&)>> draw_map
    {
        { type_id(square).hash(), drawSquare }
    };
    
    void draw(shape& obj)
    {
         // This requires the RTTI (Run-time type information) to be available.
         auto it = draw_map.find(type_id(obj).hash());
    
         if (it == draw_map.end())
             throw std::exception(); // throw some exception
         (*it)(obj);
    }
    

    NOTE: if you are using g++ 4.7, be warned unordered_map has been shown to have performance issues.

Otros consejos

This is pretty much the classic demonstration of when you want a virtual function. Define a draw in your base class, then override it in each derived class. Then to draw all the objects, you step through the collection and call the draw() member for each.

class shape { 
// ...
    virtual void draw(canvas &c) = 0;
};

class square : public shape {
    int x, y, size;
// ...
    virtual void draw(canvas &c) { 
         c.move_to(x, y);
         c.draw_to(x+size, y);
         c.draw_to(x+size, y+size);
         c.draw_to(x, y+size);
         c.draw_to(x, y);
    }
};

...and so on for each type of shape you care about.

Edit: using a strategy class, you'd end up with code vaguely along this line:

template <class draw>
class shape {
// ...
    virtual void draw(canvas &c) = 0;
};

template <class d>
class square : public shape<d> { 
    // ...
    virtual void draw(canvas &c) { 
        d.square(x, y, size, c);
    }
};

Another possibility would be to use a Visitor pattern. This is typically used when you need/want to traverse a more complex structure instead of a simple linear sequence, but could be used here as well. This is enough more complex that it's probably a bit much to go into here, but if you search for "Visitor pattern", you should turn up a fair amount of material.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top