У меня есть проблема, когда нам нужно иметь несколько форм, таких как круг и квадрат, которые могут быть расположены на плоской двумерной плоскости. Все формы, такие как Circle и Square, наследуются от базового класса abstact Shape; поэтому у меня есть вектор указателей на фигуры.
Тем не менее, мне нужно иметь возможность итерации по плоскости и найти любые фигуры, которые сталкиваются, так что они пересекаются или касаются друг друга. Если я получаю форму от вектора, я не знаю, является ли это квадратом или кругом, потому что он был нарезан базовому классу Shape.
Как мне лучше всего решить эту проблему?
#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
Вы должны использовать полиморфизм. Добавьте виртуальный метод в класс Shape:
class Shape {
...
virtual bool intersects(Shape const* otherShape);
...
}
Затем вы реализуете его для каждой другой формы. Тогда, если он используется как:
Shape* A = getShapeA();
Shape* B = getShapeB();
if (A->intersects(B))
doSomething();
Вызывается правильная версия, т.е. Если A
является Circle
, вызывается Circle
Circle::intersects
. Но там вы все еще не знаете, какая форма B
самом деле. Вы можете найти это, пытаясь сделать динамический бросок:
Circle* circle = dynamic_cast<Circle*>(otherShape);
if (circle)
intersectsCircle(circle);
dynamic_cast
для каждой фигуры, пока одна из них не работает? Это далеко не оптимально.
Ваши классы не были нарезаны. Это приведет к разрезаемому объекту:
vector<Shape> vec;
Circle circ;
vec.push_back(circ);
http://en.wikipedia.org/wiki/Object_slicing
В вашем экземпляре объекты-объекты остаются целыми, а указатели указывают на целые объекты, но почти наверняка верно, что для вычисления пересечений вам нужно будет сделать некоторое понижение. Хотя это должно быть сделано как можно меньше, это не само преступление.
Лучше всего было бы предложить метод в базовом классе, чтобы вернуть значение, указывающее тип объекта - возможно, использовать перечисление, - и использовать его для того, чтобы опустить конкретный указатель Shape
или ссылку на указатель/ссылку на правильный производный тип.
Абстрактный метод в базовом классе Shape
такой как bool Intersects( const Shape& obj )
может быть переопределен производными классами, переопределяет параметр вниз для правильного производного типа.
В качестве альтернативы вы можете предпочесть глобальный/статический метод, принимающий две формы, или конфиденциально реализовать этот метод и вызвать его из метода экземпляра Intersects()
(Обнаружение пересечений - не совсем тривиальная задача. :-))
Вот еще один метод, который не требует динамических бросков (или любого явного приведения вообще) или уродливого перечисления, перечисляющего подклассы. Он основан на двойной отправке, которая в основном работает, пройдя два виртуальных метода, чтобы определить типы двух объектов, над которыми вы хотите работать.
#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 = □
shapeA->intersect(shapeA);
shapeA->intersect(shapeB);
shapeB->intersect(shapeA);
shapeB->intersect(shapeB);
}
Обратите внимание, что здесь вам все равно нужно указать все возможные подклассы в базовом классе, но в этом случае в виде перегрузок intersect
для каждого базового класса. Если вы не можете добавить все (скажем, вы создаете class Triangle: public Shape
, но не Shape::intersect(Triangle*)
), вы получите бесконечные циклы вызовов.
Также обратите внимание, что в этом примере я сделал "тройную" отправку, поэтому мне не нужно реализовывать логику, чтобы пересечь Circle
с Square
два раза.