简体   繁体   中英

How can I iterate over a vector of base class objects?

I have an problem where we need to have a number of shapes, such as a Circle and Square which can be position on a flat, 2 dimensional plane. All shapes, such as Circle and Square inherit from an abstact base class Shape; as such I have a vector of pointers to shapes.

However, I need to be able to iterate over the plane and find any shapes which collide, such that they are intersecting or touching. If I get a shape from the vector, I don't know if it is a Square or a Circle because it has been sliced to the Shape base class.

How would I be best solving this problem?

#ifndef Plane_h
#define Plane_h

#include <vector>
#include "Shape.h"

class Plane {
public:
    Plane(std::vector<Shape*>);
    Plane(const Plane&);
    ~Plane();

    void add(Shape*);
    std::vector<Shape*> all() const;

protected:
    std::vector<Shape*> shapes;
};

#endif

Your classes have not been sliced. This would result in a sliced object:

vector<Shape> vec;
Circle circ;
vec.push_back(circ);

http://en.wikipedia.org/wiki/Object_slicing

In your instance the instantiated objects remain whole and the pointers point to whole objects - but it is almost certainly true that in order to calculate intersections you will need to do some downcasting. While this is to be done as little as possible it's not a crime in itself.

Your best bet would be to provide a method in the base class to return a value indicating the object type - maybe use an enumeration - and use that to downcast a particular Shape pointer or reference to a pointer/reference to the correct derived type.

An abstract method in the base Shape class like bool Intersects( const Shape& obj ) could be overridden by the derived classes, the overrides downcasting the parameter to the correct derived type.

Alternatively you might prefer to provide a global/static method taking two shapes, or privately implement that method and call it from the instance method Intersects()

( Detecting the intersections is not exactly a trivial task. :-) )

You have to use polymorphism. Add a virtual method on your Shape class:

class Shape {
  ...
  virtual bool intersects(Shape const* otherShape);
  ...
}

Then you implement it for each different shape. Then if it's used like:

Shape* A = getShapeA();
Shape* B = getShapeB();
if (A->intersects(B))
  doSomething();

The correct version is called, ie if A is a Circle , Circle::intersects is called. But in there, you still don't know what shape B actually is. You can find this out by trying to do a dynamic cast:

Circle* circle = dynamic_cast<Circle*>(otherShape);
if (circle)
  intersectsCircle(circle);

Here is another method, that does not need dynamic casts (or any explicit cast at all), or an ugly enum listing the subclasses. It is based on double dispatch , which basically works by going through two virtual methods in order to determine the types of the two objects you want to work on.

#include <iostream>
using namespace std;

class Circle;
class Square;

struct Shape
{
  virtual void intersect(Shape* otherShape) = 0;
  virtual void intersect(Circle* otherCircle) = 0;
  virtual void intersect(Square* otherSquare) = 0;
};

struct Circle : public Shape
{
  virtual void intersect(Shape* otherShape)
  {
    otherShape->intersect(this);
  }

  virtual void intersect(Circle* otherCircle)
  {
    cout << "Intersecting Circle with Circle" << endl;
  }

  virtual void intersect(Square* otherSquare)
  {
    cout << "Intersecting Circle with Square" << endl;
  }
};

struct Square : public Shape
{
  virtual void intersect(Shape* otherShape)
  {
    otherShape->intersect(this);
  }

  virtual void intersect(Circle* otherCircle)
  {
    otherCircle->intersect(this);
  }

  virtual void intersect(Square* otherSquare)
  {
    cout << "Intersecting Square with Square" << endl;
  }

};

int main()
{
  Circle circle;
  Square square;

  circle.intersect(&square);

  Shape* shapeA = &circle;
  Shape* shapeB = &square;

  shapeA->intersect(shapeA);
  shapeA->intersect(shapeB);
  shapeB->intersect(shapeA);
  shapeB->intersect(shapeB);
}

Note that here you still have to list all possible subclasses within the base class, but in this case in the form of overloads of the intersect for each base class. If you fail to add all (say, you make a class Triangle : public Shape , but no Shape::intersect(Triangle*) ), you end up with infinite call loops.

Also note that in this example I did a 'triple' dispatch, so I don't have to implement the logic to intersect a Circle with a Square twice.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM