Suppose you have something like the following:
class Shape // base class
{
private:
bool degenerate, ill_defined;
...
public:
bool isVoid () { return false; }
bool isCircle() { return false; }
bool isPoint () { return false; }
bool isPlane () { return false; }
bool isSphere() { return false; }
...
};
class Void : public Shape {
...
}
class Plane : public Shape
{
public:
bool isPlane() { return !degenerate && !ill_defined; }
bool isVoid () { return ill_defined; }
...
operator Void () throw() {
if (isVoid()) return Void();
else throw ...; //some error
}
...
}
class Point : public Shape {
private:
double radius;
...
public:
bool isPoint() { return !ill_defined; }
bool isVoid () { return ill_defined; }
...
operator Void () throw() { ... }
...
}
class Circle : public Shape // similar to the rest
class Sphere : public Shape // similar to the rest
The intersection between a Plane and a Sphere can be either
- a
Circle(if the plane “cuts through” the sphere) - a
Point(if the plane “just touches” the sphere) - a
Void(if the sphere lies entirely above or below the plane)
I was wondering how to best define and use an intersection between a Plane and a Sphere, since the return type of the hypothetical
intersect(const Sphere& S, const Plane& P)
method/free function is unknown at compile time.
I never encountered this situation before, so I looked up some possible ways to do it. I came across this question which recommends boost::variant. In my situation, that would look like
boost::variant<Void, Point, Circle> intersection =
intersect(const Sphere& S, const Plane& P);
But this has three drawbacks:
- It’s fugly.
-
something like
intersection.radiuscannot be used as-is, sincePointandVoiddo not have aradius. You’d have to do something likeif (intersection.isPoint()){ ... } else if (intersection.isCircle()) { // possibly cast to Point if degenerate, otherwise: double R = intersection.radius; ... } // etc. -
The user of a library implementing all these shapes would always have to know what types could be returned by intersecting two shapes. That is, the user would always have to declare something of type
boost::variant<scope::Void, scope::Point, scope::Circle>which is complicated and just plain ugly. Fortunately, c++11 has theautokeyword for that. Alternatively, you could use a member like soclass Sphere : public Shape { ... public: boost::variant<scope::Void, scope::Point, scope::Circle> intersect_type; intersect_type intersect(const Plane& P); ... };so that we can use
Sphere::intersect_type t = S.intersect(P);where
Sis an instance ofSphereandPan instance ofPlane. But then still we’d have to have separate handling of all possible types:if (intersection.isPoint()){ ... } else if (intersection.isCircle()){ intersection.radius; } // etc.
so that the complexity we tried to take away from the user is actually still there.
I feel like I’m missing something here. Perhaps there is a smarter way to implement my Shape baseclass? Or should I create a separate, dedicated Intersect class? What solution is most elegant, efficient and effective for this situation?
Off-hand:
The
isXXXX()predicate methods seem like a code smell to me. You’d doif (dynamic_cast<Circle*>(shapePtr))with RTTI usuallyvariant::which()and/orvariant::type()to discriminate the variant’s stored valueTo your question:
There are several possible approaches.
The classic OO approach would be to just derive everything from Shape and always return a
std::unique_ptr<Shape>(or similar).However, obviously, you can do modern C++ static OO, in which case you’d end up with something similar to the variant. You’d then write a visitor to handle different cases:
(live on http://liveworkspace.org/code/bad329cb40d94a21531e1153f4c0877b)
Output: