Upcoming Events
Southwest Gaming Expo
11/20 - 11/22 @ Dallas, TX

Workshop on Network and Systems Support for Games (NetGames 2009)
11/23 - 11/25 @ Paris, France

ICIDS 2009 Interactive Storytelling
12/9 - 12/11 @ Guimarães, Portugal

Global Game Jam
1/29 - 1/31  

More events...


Quick Stats
7037 people currently visiting GDNet.
2341 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!



Link to us

Link to us

  Intel sponsors gamedev.net search:   

Thanks But No Thanks, C++

Reading over the last few paragraphs, the astute programmer will notice an interesting point: At no time did we actually need runtime polymorphism. It helped us write our DrawAShapeOverAndOver() function by letting us write a single function that would work for all classes derived from Shape, but in each case the run-time lookup could have been done at compile-time.

Bearing this in mind, let's approach polymorphism again, but this time with more caution. We won't be making the DrawOutline() method virtual again, since so far that has done us no good at all. Instead, let's rewrite DrawAShapeOverAndOver() as a templated function. This way we are not forced to write both DrawAShapeWhichIsARectangleOverAndOver() and DrawAShapeWhichIsACircleOverAndOver().

template<typename ShapeType>
void DrawAShapeOverAndOver(ShapeType* myShape)
{
  for(int i=0; i<10000; i++)
  {
    myShape->DrawOutline();
  }
}

Rectangle *myRectangle = new Rectangle;
DrawAShapeOverAndOver(myRectangle);
delete myRectangle;

Hey! Now we're getting somewhere! We can pass in any kind of Shape to DrawAShapeOverAndOver(), just like before, except this time there is no runtime checking of myShape's type! Interestingly enough, Rectangle and Circle don't even have to be derived from Shape. They just have to be classes with a DrawOutline() function.

Making Our Lives More Difficult

Let's go back to our original example, but this time let's make more use of the other features of subclassing. After all, derived classes and base classes with no private members, nontrivial constructors, or internal calls of virtual functions are a rather severe oversimplification of subclassing. Let's also supply an actual implementation of DrawOutline() and DrawFill(), albeit using a completely fictional Graphics object that will nevertheless allow us to illustrate how functions in derived classes may use functions in base classes.

Now, let's pull out the big guns.

class Shape
{
public:
  Shape(const Point &initialLocation,
     const std::string &initialOutlineColor,
     const std::string &initialFillColor) :
    location(initialLocation),
    outlineColor(initialOutlineColor),
    fillColor(initialFillColor)
  {
  }

  virtual ~Shape()
  {
  }

  virtual void DrawOutline() const = 0;
  virtual void DrawFill() const = 0;

  void SetOutlineColor(const std::string &newOutlineColor)
  {
    outlineColor = newOutlineColor;
  }

  void SetFillColor(const std::string &newFillColor)
  {
    fillColor = newFillColor;
  }

  void SetLocation(const Point & newLocation)
  {
    location = newLocation;
  }

  const std::string &GetOutlineColor() const
  {
    return outlineColor;
  }

  const std::string &GetFillColor() const
  {
    return fillColor;
  }

  const Point &GetLocation() const
  {
    return location;
  }

  void DrawFilled() const
  {
    DrawOutline();
    DrawFill();
  }

private:
  std::string outlineColor;

  std::string fillColor;

  Point location;
};

class Rectangle : public Shape
{
public:
  Rectangle(const Point &initialLocation,
       const std::string &initialOutlineColor,
       const std::string &initialFillColor(),
       double initialHeight,
       double initialWidth) :
    Shape(initialLocation, initialOutlineColor,
          initialFillColor),
    height(initialHeight),
    width(initialWidth)
  {
  }

  virtual ~Rectangle()
  {
  }

  virtual void DrawOutline() const
  {
    Graphics::SetColor(GetOutlineColor());
    Graphics::GoToPoint(GetLocation());
    Graphics::DrawRectangleLines(height, width);
  }

  virtual void DrawFill() const
  {
    Graphics::SetColor(GetOutlineColor());
    Graphics::GoToPoint(GetLocation());
    Graphics::DrawRectangleFill(height, width);
  }

  void SetHeight(double newHeight)
  {
    height = newHeight;
  }

  void SetWidth(double newWidth)
  {
    width = newWidth;
  }

  double GetHeight() const
  {
    return height;
  }

  double GetWidth() const
  {
    return width;
  }

private:
  double height;
  double width;
};

class Circle : public Shape
{
public:
  Circle(const Point &initialLocation,
      const std::string &initialOutlineColor,
      const std::string &initialFillColor,
      double initialRadius) :
    Shape(initialLocation, initialOutlineColor,
          initialFillColor),
    radius(initialRadius)
  {
  }

  virtual ~Circle()
  {
  }

  virtual void DrawOutline() const
  {
    Graphics::SetColor(GetOutlineColor());
    Graphics::GoToPoint(GetLocation());
    Graphics::DrawCircularLine(radius);
  }

  virtual void DrawFill() const
  {
    Graphics::SetColor(GetOutlineColor());
    Graphics::GoToPoint(GetLocation());
    Graphics::DrawCircularFill(radius);
  }

  void SetRadius(double newRadius)
  {
    radius = newRadius;
  }

  double GetRadius() const
  {
    return radius;
  }
private:
  double radius;
};

Whew! Let's see what we added there. First of all, Shape objects now have data members. All Shape objects have a location, and an outlineColor and a fillColor. In addition, Rectangle objects have a height and a width, and Circle objects have a radius. Each of these members has corresponding getter and setter functions. The most important new addition is the DrawFilled() method, which draws both the outline and the fill in one step by delegating these methods to the derived class.





We Can Rebuild It; We Have The Technology

Contents
  Introduction
  Thanks But No Thanks, C++
  We Can Rebuild It; We Have The Technology

  Printable version
  Discuss this article