4

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

2 Answers2

2

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.

Adrian
  • 10,246
  • 4
  • 44
  • 110
  • I thought of it but what I was thinking is to keep the drawing away separate from the base class. A completely different class that does the drawing. –  Jun 22 '13 at 00:34
  • You could use a something like a delegate to do what you are looking for. I.e. function pointer or a or functor. However, what is the reasoning for this? This type of separation should have a good reason for it. – Adrian Jun 22 '13 at 03:21
  • @Wajih, I've added some examples as to how you can separate the drawing code from the implementation class. – Adrian Jun 22 '13 at 04:15
  • Adrian- Thanks for the some neat code snippets. THe reason for such separation is that I dont want my objects bother about drawing at all. I have a class that takes care of drawing objects. The problem as per Jerry's response and yours is to follow OO design. Fine but my separation is based on the fact that I need to maintain some old code. And cannot change the base class implementation! I will try! –  Jun 22 '13 at 14:57
  • @Wajih, If you are not able to modify the base, you can just inherit from the shape derived objects and have these new classes implement the drawing as I showed in point 4. If that would require changing a lot of code elsewhere, due to searching for every instance that you have created and renaming them to the new derived, my new point 5 *might* be a viable work around. – Adrian Jun 22 '13 at 16:31
  • NOTE: if you are using g++ 4.7, **be warned** `unordered_map` has been shown to have [performance issues](http://stackoverflow.com/a/11634689/1366368). – Adrian Jun 26 '13 at 16:51
1

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.

Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • But what if I want to keep my drawing away from the base class? In a different class completely? –  Jun 22 '13 at 00:33
  • @Wajih: the OO answer is that drawable objects *should* know how to draw themselves (and this is one of those places that an OO design probably will work quite well). If you really insist on something else, you could have (for one example) some `dynamic_cast`s to determine types, then use function overloading to handle drawing for different types. I'd tend to stick with the OO design, and if you want to keep the drawing *per se* separate, pass something like a template parameter that each object can use to do drawing. – Jerry Coffin Jun 22 '13 at 00:39
  • 1
    @Wajih: the bottom line, however, is pretty simple: this is really *the* type of problem OO and polymorphism were designed to solve, and it remains about the best (widely available) way to solve this sort of problem. – Jerry Coffin Jun 22 '13 at 00:42
  • thanks for the tip. I guess I have to use the dynamic_cast option but I have to look into it. Looks like lots of clunky code already. May be you are right. The OO answer could be the final verdict! Thanks –  Jun 22 '13 at 00:42
  • A template parameter (which you'd use to pass what's really a Strategy class) can keep the drawing itself entirely separate. All you're putting into your base class (and probably derived classes as well) is the basic notion that a shape can be drawn. Essentially everything else can be delegated to the Strategy class. – Jerry Coffin Jun 22 '13 at 00:46
  • Indeed that works, but what is the reasoning for the separation? Are you trying to get away from a vtable? If so, you might want to do something like what was done in ATL (an example can be found at [this answer](http://stackoverflow.com/a/15577595/1366368)). If you are separating the `Shape` from the `Drawing of shape`, you should really have a good reason, otherwise you may decouple the code to the point where you may introduce readability, debugging and usability issues. – Adrian Jun 22 '13 at 03:11
  • BTW, `d.square(x, y, size, c);` won't work since d is a class, not an object. You would have to declare `d::square()` as a static member function and call it like so: `d::square(x, y, size, c);` – Adrian Jun 22 '13 at 03:17
  • @Adrian: Oops -- quite true (or pass/create an object of type `d` at some point). – Jerry Coffin Jun 22 '13 at 03:56
  • @JerryCoffin: Drawing objects should know how to render themselves into some sort of "lingua franca" which is understood by all the contexts (screens, printers, etc.) where code might might want an object to be rendered. To render things optimally in the general case would require double dispatch, since rendering contexts will likely differ in the kinds of shapes they know how to draw. If after the interface language is defined, some superior alternative to Bézier curves is invented, it should be possible for a future render engine which understands such curves... – supercat Nov 26 '13 at 17:36
  • ...to receive the defining data from `Shape` objects which encapsulate such curves and perform such rendering themselves, while other rendering engines would be given a list of line segments, Béziers, or other such an approximation. True double dispatch probably wouldn't be necessary in practice, but figuring out how one could use single dispatch achieve the necessary functionality in the face of future expansion could be interesting (e.g. perhaps assign GUIDs to various primitive shapes or drawing concepts an engine could support, a query method for an engine to list things it supports). – supercat Nov 26 '13 at 17:41
  • @supercat: nearly every real case of which I'm aware (e.g., Windows GDI device context, D2D/D3D device contexts, X Graphics context, etc.) provides a generic interface, and differences in how things like curves are drawn get hidden. So, while I agree that it's an interesting theoretical problem, I think it's largely irrelevant to a question of how to do drawing in most real programs. – Jerry Coffin Nov 26 '13 at 17:41
  • @JerryCoffin: Curves were just one example. More current examples would be various kinds of 3d shaded or texture-mapped shapes, or things like compressed graphic images. If after the rendering interface is standardized, some new image format becomes popular, the rendering interface isn't going to know about it, so any graphic object encapsulating it will have to be able to convert it to some other format, possibly losing quality in the process. As rendering engines improve, there should be a path to shift toward having rendering engines do the work when they're able to do so. – supercat Nov 26 '13 at 18:04