Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Adding virtual functions without modifying the original classes

Let's say we already have a hierarchy of classes, e.g.

class Shape { virtual void get_area() = 0; };
class Square : Shape { ... };
class Circle : Shape { ... };
etc.

Now let's say that I want to (effectively) add a virtual draw() = 0 method to Shape with appropriate definitions in each sub-class. However, let's say I want to do this without modifying those classes (as they are part of a library that I don't want to change).

What would be the best way to go about this?

Whether or not I actually "add" a virtual method or not is not important, I just want polymorphic behaviour given an array of pointers.

My first thought would be to do this:

class IDrawable { virtual void draw() = 0; };
class DrawableSquare : Square, IDrawable { void draw() { ... } };
class DrawableCircle : Circle, IDrawable { void draw() { ... } };

and then just replace all creations of Squares and Circles with DrawableSquares and DrawableCircles, respectively.

Is that the best way to accomplish this, or is there something better (preferably something that leaves the creation of Squares and Circles intact).

Thanks in advance.

like image 689
Peter Alexander Avatar asked Feb 28 '10 20:02

Peter Alexander


People also ask

Do all virtual functions need to be implemented in derived classes?

Derived classes do not have to implement all virtual functions themselves. They only need to implement the pure ones. That means the Derived class in the question is correct.

Is it mandatory to override the virtual function?

It is not mandatory for the derived class to override (or re-define the virtual function), in that case, the base class version of the function is used.

Does a virtual function need to be defined in base class?

Virtual functions in a base class must be defined unless they are declared using the pure-specifier.

Is redefine virtual function in derived class mandatory?

Redefining the virtual function in the derived class is optional, but it needs to be defined in the base class. The function call resolving is done at run-time. You can create a virtual destructor but not a constructor.


2 Answers

(I do propose a solution down further... bear with me...)

One way to (almost) solve your problem is to use a Visitor design pattern. Something like this:

class DrawVisitor
{
public:
  void draw(const Shape &shape); // dispatches to correct private method
private:
  void visitSquare(const Square &square);
  void visitCircle(const Circle &circle);
};

Then instead of this:

Shape &shape = getShape(); // returns some Shape subclass
shape.draw(); // virtual method

You would do:

DrawVisitor dv;
Shape &shape = getShape();
dv.draw(shape);

Normally in a Visitor pattern you would implement the draw method like this:

DrawVisitor::draw(const Shape &shape)
{
  shape.accept(*this);
}

But that only works if the Shape hierarchy was designed to be visited: each subclass implements the virtual method accept by calling the appropriate visitXxxx method on the Visitor. Most likely it was not designed for that.

Without being able to modify the class hierarchy to add a virtual accept method to Shape (and all subclasses), you need some other way to dispatch to the correct draw method. One naieve approach is this:

DrawVisitor::draw(const Shape &shape)
{
  if (const Square *pSquare = dynamic_cast<const Square *>(&shape))
  {
    visitSquare(*pSquare);
  }
  else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape))
  {
    visitCircle(*pCircle);
  }
  // etc.
}

That will work, but there is a performance hit to using dynamic_cast that way. If you can afford that hit, it is a straightforward approach that is easy to understand, debug, maintain, etc.

Suppose there was an enumeration of all shape types:

enum ShapeId { SQUARE, CIRCLE, ... };

and there was a virtual method ShapeId Shape::getId() const = 0; that each subclass would override to return its ShapeId. Then you could do your dispatch using a massive switch statement instead of the if-elsif-elsif of dynamic_casts. Or perhaps instead of a switch use a hashtable. The best case scenario is to put this mapping function in one place, so that you can define multiple visitors without having to repeat the mapping logic each time.

So you probably don't have a getid() method either. Too bad. What's another way to get an ID that is unique for each type of object? RTTI. This is not necessarily elegant or foolproof, but you can create a hashtable of type_info pointers. You can build this hashtable in some initialization code or build it dynamically (or both).

DrawVisitor::init() // static method or ctor
{
  typeMap_[&typeid(Square)] = &visitSquare;
  typeMap_[&typeid(Circle)] = &visitCircle;
  // etc.
}

DrawVisitor::draw(const Shape &shape)
{
  type_info *ti = typeid(shape);
  typedef void (DrawVisitor::*VisitFun)(const Shape &shape);
  VisitFun visit = 0; // or default draw method?
  TypeMap::iterator iter = typeMap_.find(ti);
  if (iter != typeMap_.end())
  {
    visit = iter->second;
  }
  else if (const Square *pSquare = dynamic_cast<const Square *>(&shape))
  {
    visit = typeMap_[ti] = &visitSquare;
  }
  else if (const Circle *pCircle = dynamic_cast<const Circle *>(&shape))
  {
    visit = typeMap_[ti] = &visitCircle;
  }
  // etc.

  if (visit)
  {
    // will have to do static_cast<> inside the function
    ((*this).*(visit))(shape);
  }
}

Might be some bugs/syntax errors in there, I haven't tried compiling this example. I have done something like this before -- the technique works. I'm not sure if you might run into problems with shared libraries though.

One last thing I'll add: regardless of how you decide to do the dispatch, it probably makes sense to make a visitor base class:

class ShapeVisitor
{
public:
  void visit(const Shape &shape); // not virtual
private:
  virtual void visitSquare(const Square &square) = 0;
  virtual void visitCircle(const Circle &circle) = 0;
};
like image 71
Dan Avatar answered Sep 22 '22 00:09

Dan


What you're describing is somewhat like the decorator pattern. Which is very suitable to change runtime behaviour of existing classes.

But I don't really see how to implement your practical example, if shapes have no way to be drawn, then there's no way to change drawing behaviour at runtime either...

But I suppose this is just a very simplified example for stackoverflow? If all the basic building blocks for the desired functionality are available, then implementing the exact runtime behaviour with such a pattern is certainly a decent option.

like image 44
Pieter Avatar answered Sep 21 '22 00:09

Pieter