Advertisement

A little question about polymorphism

Started by May 11, 2018 04:18 AM
3 comments, last by mic_k 6 years, 4 months ago

Hi all,

I was reading Game engine architecture and with that, brushing up some coding theory.

If I recall correct, polymorphism can help you for example to achieve the following:

 

- create a parent class with a virtual function, that needs to implemented in child/ inherited classes

- let's take a shape for example

- I have a triangle and square class implementing their own Draw function, like declared a virtual function in the shape class

 

Now my question is, how do I iterate through all child class objects?

Simplified code:


class Shape
{
public:
	unsigned int someVar;

	virtual void Draw();
};


class Triangle : public Shape
{
public:
	void Draw();
};

class Square : public Shape
{
public:
	void Draw();
};


// some program

std::vector<Triangle> myTriangles;
std::vector<Square> mySquares;

// generate some triangles and squares

for(size_t i=0;i<myTriangles.size();++i) myTriangles[i].Draw();
for(size_t j=0;j<mySquares.size();++j) mySquares[j].Draw();

I believe it should be possible to do the last 2 lines at once.

How would I iterate through them at once, using the advantage that polymorphism should bring here?

The specific case of drawing is a bad example because it follows a highly inefficient data path. Drawing is best done in large batches with a small number of calls because each call has an overhead. Calling each object results in tiny batches of size 1 and an enormous number of draw calls, which is the worst case for drawing hardware.

 

There are generally two patterns used.

 

In one situation you would use a shared interface and process them as a group. Instead of making Shape have Draw, you might have an interface called "Drawable"

For a C++ loop, you might have something like this:


...
for( size_t i = 0; i < SceneList.Size(); i++ ) // I'd prefer a newer ranged for loop, but whatever works.
{
  if( Drawable* drawable = dynamic_cast<Drawable*>SceneList.GetItem[i] )
  {
    drawable->Draw();
  }
}

There is a small cost to be paid for the dynamic cast, but since it will return a null pointer if the cast fails and it gracefully handles a bunch of data hierarchy problems, the cost is generally something you would pay anyway if you're detecting that an operation is supported.

 

Alternatively, if you don't want to pay for a conversion or a type test you can provide a function that is called for every object rather than just the specific interface functions, and implement the function differently based on the derived type of object:

For that style of system:


//Do this:
for( size_t actorIdx = 0; actorIdx < actors.count; ++actorIdx )
{
  actors[actorIdx]->DoPrimaryAction();
}

//Not this:
for( size_t actorIdx = 0; actorIdx < actors.count; ++actorIdx )
{
  if(actors[actorIdx]->type == ActorType.Wizard)
    actors[actorIdx]->DoFireball();
  else if(actors[actorIdx]->type == ActorType.Warrior)
    actors[actorIdx]->DoSwordSwing();
  else if(actors[actorIdx]->type == ActorType.Rogue)
    actors[actorIdx]->DoKnifeStab();
  else if(actors[actorIdx]->type == ActorType.Archer)
    actors[actorIdx]->DoFireBow();
...
}

In both cases your code would not know or care about the concrete class it has.  There are a collection of things that all implement the same interface. You have a factor method somewhere that created the things for you. The things might be triangles or squares or circles, but you don't care because you only care about shapes.  Or the things might be wizards and warriors and rogues, but you don't care because you only care about actor objects.  When you follow SOLID principles, you should generally be focused on abstractions, not the actual objects.

Advertisement

Polymorphism works with pointers


std::vector< shape*> shapes;
  shapes.push_back( &atriangle);
  shapes.push_back( &asquare);
  //iterate and call virtual draw
  

 

I think the pointer way can work, as suggested above. Since Triangle and Square both is a Shape, we can use one list/vector to hold all kind of Shape.

And maybe we use pure virtual function: virtual void Draw() = 0; at the parent class Shape.

This topic is closed to new replies.

Advertisement