I want to use inheritance to have an object treated in a different way depending on where in the hierarchy it falls
(similar to this C# question)
Assume you build a hierarchy of Shape objects like:
class Shape {} ;
class Sphere : public Shape {} ;
class Triangle : public Shape {} ; ...
You then equip a Ray class with methods like:
class Ray
{
Intersection intersects( const Sphere * s ) ;
Intersection intersects( const Triangle * t ) ;
};
You store an array of assorted Shape* of various types and call
vector<Shape*> shapes ; ...
//foreach shape..
Intersection int = ray.intersects( shapes[ i ] )
But you get the compiler error
error C2664: 'Intersection Ray::intersects(const Sphere *) const' : cannot convert parameter 1 from 'Shape *const ' to 'const Sphere *'
What did you do wrong?
Is the only way to do it the other way around, with
class Shape
{
virtual Intersection intersects( const Ray* ray )=0 ;
} ;
then each class override intersects? Then calls to
//foreach shape..
Intersection int = shapes[i]->intersects( ray ) ;
Can you do it the first way I showed or NEVER?
You have to do it the other way around. Overload resolution takes place at compile-time, when the type of what you're calling it with is a Shape*.
No, you can't do it the first way. Overload resolution in C++ is based on the static types of function arguments. It is resolved at compile time. In your example the static type is Shape * and there's no function in your class that would accept a Shape * (hence the error). The compiler doesn't care that your pointer might actually point to a Sphere at run time.
To implement what you are trying to implement you have to "channel" your calls through a facility that relies on dynamic types of the objects, i.e. through virtual function calls, which is what you do in your second example.
Your second example is a bit simplified, since the type of one of the objects involved is known at compile time (Ray). In a more complicated case both objects involved in an "intersection" could be dynamically type. If you care to handle something like that you can use so called "double dispatch" technique (search for it).
May be you can pull this with RTTI information. I haven't done it but it might be possible.
class Ray
{
Intersection intersects( const Sphere * s ) ;
Intersection intersects( const Triangle * t ) ;
Intersection intersects( const Shape * s ) {
//act on RTTI info, or
//use dynamic_cast to other types and check if the result is NULL or not
}
};
Related
I'm learning C++ and I came across a problem that I can tackle with my previous programming experience (mainly C and Java; some but limited OOP experience), but I'd like to know what would be a proper, modern C++ solution to it. The problem concerns inheritance and derived classes' versions of a virtual function with different return types. Based on multiple Stack Overflow threads such a thing isn't possible. So how should I go about the following?
To practice C++ features, I'm writing a ray tracer. I have a virtual base class Object and derived classes Polyhedron and Polygon to describe the objects Rays of light can interact with. (In reality I have intermediate virtual classes Solid and Face, and derived classes Sphere, Cylinder, Circle alongside Polyhedron and Polygon, but let's forget about them here to keep things simple.) Currently, I've only implemented emission and absorption of light, i.e., a Ray only goes straight without any refraction or reflections. Absorption within a Polyhedron is proportional to intensity (exponential decay), so I have to figure out the objects a Ray passes through and integrate the Ray's intensity forward from its source to where it hits the detector. I have a vector std::vector<std::shared_ptr<Intersection>> intersections to store all these intersections of a Ray with the objects in a simulated scene. An intersection needs to contain the intersection Points, the intersected Polygon faces and the Polyhedron itself for a Polyhedron object, or alternatively the intersection Point and the Polygon face itself for a Polygon object. Consequently, I'd like to have derived classes Intersection_Polyhedron and Intersection_Polygon to override the call to Intersection::modulate_intensity(const double intensity_before) const which is supposed to return a Ray's intensity after passing the object in question. In other words, I'd like to avoid checking the type of the intersected objects and instead take advantage of inheritance when calculating the modulation to a Ray's intensity.
I would like to have each Ray simply loop through a vector std::vector<std::shared_ptr<Object>> objects containing all the objects in a simulated scene, call the virtual function Object::get_intersection(const Ray& ray) const and get either Intersection_Polyhedron or Intersection_Polygon in return based on the type of the intersection (if it's with a Polyhedron or a Polygon). Pointers to these derived intersection objects would be pushed back into intersections, intersections would be sorted based on the distance from the Ray's origin and then looped through to call and override Intersection::modulate_intensity() to determine a Ray's final intensity on the detector. To me, this would sound like the C++/OOP way of achieving this, but it doesn't seem possible because derived classes' versions of a base class's virtual function must all have the same return type. So how should I do it?
(Currently, I return a singular type of Intersection from get_intersection() for both Polyhedrons and Polygons. As its members, an Intersection has vectors for intersection Points and intersected std::shared_ptr<Polygon> faces, and an std::shared_ptr<Polyhedron> (which is a nullptr for Polygons as there's no bulk). To distinguish between intersections of Polyhedrons and Polygons, I simpy check if there are one or two intersection Points. This isn't too inelegant, but modern C++ has to offer a better way of achieving this with inheritance, right?)
Some very C++-like pseudocode to further clarify what I'd like to achieve:
// ...
// create objects in a scene
std::vector<std::shared_ptr<Object>> objects;
// ...
// find a ray's intersections with the objects
std::vector<std::shared_ptr<Intersection>> intersections;
for(const auto& object : objects) {
// virtual class Object's function overridden with that of Polyhedron or Polygon
// returns std::shared_ptr<Intersection_Polyhedron> or std::shared_ptr<Intersection_Polygon> based on type of object
auto intersection = object->get_intersection(ray);
intersections.push_back(intersection);
}
// sort the intersections with std::sort and a lambda expression
// ...
// calculate a ray's intensity
double intensity = 0.0;
for(const auto& intersection : intersections) {
// virtual class Intersection's function overridden with that of Intensity_Polyhedron or Intensity_Polygon
intensity = intersection->modulate_intensity(intensity);
}
// ...
Returning interface is fine in general:
class Ray;
struct Intersection
{
virtual ~Intersection() = default;
virtual double modulate_intensity(double intensity) = 0;
};
struct Intersection_Polygon : Intersection
{
double modulate_intensity(double intensity) override {/**/}
};
struct Intersection_Polyhedron : Intersection
{
double modulate_intensity(double intensity) override {/**/}
};
struct Object
{
virtual ~Object() = default;
virtual std::shared_ptr<Intersection> get_intersection(const Ray&) = 0;
};
struct Polygon : Object
{
std::shared_ptr<Intersection> get_intersection(const Ray&) override {
return std::make_shared<Intersection_Polygon>();
}
};
struct Polyhedron : Object
{
std::shared_ptr<Intersection> get_intersection(const Ray&) override {
return std::make_shared<Intersection_Polyhedron>();
}
};
Return type can be improved with covariance, but C++ only handle it for references and (non-smart) pointers. So it requires some boiler plate to simulate it for smart pointers:
struct Object
{
virtual ~Object() = default;
std::shared_ptr<Intersection> get_intersection(const Ray& ray)
{
return std::shared_ptr<Intersection>{get_intersection_ptr(ray)};
}
protected:
virtual Intersection* get_intersection_ptr(const Ray&) = 0;
};
struct Polygon : Object
{
std::shared_ptr<Intersection_Polygon> get_intersection(const Ray& ray)
{
return std::shared_ptr<Intersection_Polygon>{get_intersection_ptr(ray)};
}
protected:
Intersection_Polygon* get_intersection_ptr(const Ray&) override {
return new Intersection_Polygon();
}
};
template (possibly CRTP) might help to factorize boiler plate:
template <typename IntersectionType>
struct ObjectT : Object
{
std::shared_ptr<IntersectionType> get_intersection(const Ray& ray)
{
return std::shared_ptr<IntersectionType>{get_intersection_ptr(ray)};
}
protected:
IntersectionType* get_intersection_ptr(const Ray&) override {
return new IntersectionType();
}
};
struct Polygon : ObjectT<Intersection_Polygon> {};
struct Polyhedron : ObjectT<Intersection_Polyhedron> {};
Let say that I have a big class Circle with a lot of members and functions. To proceed a large amount of data I decided to create class PotentialCirlce (with only 3 members - x, y, r), do most of preprocessing based on PotentialCirlce and in the last stage create objects Circle.
a) is it correct approach? do It influence on performance or rather should I use only Circle.
It seems to me that I can use inheritance:
class potentialCircle {
protected:
point_t center;
unsigned int radius;
public:
potentialCircle(int a, int b, unsigned int r) : center{ point_t(a,b) }, radius{ r } {}
potentialCircle() = delete;
potentialCircle(const potentialCircle&) = default;
potentialCircle(potentialCircle&&) = default;
potentialCircle& operator=(const potentialCircle&) = default;
potentialCircle& operator=(potentialCircle&&) = default;
virtual ~potentialCircle() = default;
};
class Circle : public potentialCircle {
// members detected based on Hough Circle Transform
//point_t center; // coordinates of center point
point_t alternative_center; // needed when center is out of frame
//unsigned int radius; // radius
// members calculated based on Flood Fill algorithm (more realistic)
unsigned int area = 0;
float diameter = 0;
float perimeter = 0;
....
};
b) where should I put method which needs to compare two difference objects? one object of type Circle and one of PotentialCirle?
currently, I have defined below function as part of Circle
bool Circle::is_greater(const std::pair<potentialCircle, int>& point_pair) const;
but I don't have access to protected data members of potentialCircle, although Circle is inheriting from potentialCircle.
Maybe I should defined is_greater() as part of namepsace and make it a friend to Circle and potentialCircle.
Do you have better idea?
There are not really a good approach to compare objects of different types as it make little sense in practice. What would be the purpose of such comparisons.
Now even if you have a single class, if the ordering is not intransic to the type, it would be better to use an external class for sorting.
class CircleDiameterLess
{
public:
bool operator()(const Circle &lhs, const Circle &rhs)
{
return lhs.diameter < rhs.diameter;
}
};
That way, you can have multiple ways to sort data and it play nice with STL.
Another problem with your code if that it make little sense to have a class circle with a diameter that derives from a class potentialCircle with a radius. Your code will be hard to maintain because it is hard to understand.
You want to store either the diameter or the radius and compute the other one.
unsigned int get_diameter() const { return radius * 2; }
Member like alternative_center make no sense. A circle has only one center. If your class does not respect basic expectations, it will make the code hard to maintain as nobody would known that a circle has 2 centers including you in 3 months!
In a case like yours, it make make sense to add public accessors.
class potentialCircle
{
public:
unsigned int get_radius() const { return radius; }
....
};
That way, you can still make data private (or sometime protected) while having read only access to it. That way, you can write you comparison function as you wish. And in practice, if you have a class that represent a circle, you usually want at least being able to get basic properties like radius, aread, bounding rectangle by the way of a function.
Another thing is that public derivation as your (from potentialCircle) would only make senses if you have other classes that derives from it. However, if this is the case, then how would you compare the other kind of circles?
Notes:
With C++ 20, three way comparison would be even better.
I want to have a Collider interface class in which will have a overloaded -> operator to have access directy to the BoxCollider derived class. I want to have access to the members of box collider through the interface and chnage the type of collider at run-time.
So I thought of using templates:
template<typename T>
class ColliderV2 {
public:
virtual T* operator ->() = 0;
};
class BoxColliderV2 : public ColliderV2<BoxColliderV2> {
public:
float width;
float height;
BoxColliderV2* operator ->() {
return this;
}
};
int main()
{
ColliderV2<BoxColliderV2>* col = new BoxColliderV2;
(*col)->width = 1;
}
This works. But templates , as far as I know, will generate a brand new Collider class in compile-time filling T with Box Collider, correct? Thats why it worked. But later it prevents me from changing the collider type. I also thought of just making a virtual Collider class with Collider* operator->() ; overload in the derived class BoxCollider* operator->() ;
But if I tried :
Collider<BoxCollider>* col = new BoxCollider;
(*col)->width = 1; // won't work
doesn't work since Collider is not BoxCollider. And I don't want to dynamic_cast every possible collider type I could have. So, what can be done here?
As you've already found out, this doesn't work. Templates and runtime behavior are kind of contradicting mechanics. You can't create a common base class and let it act like a generic pointer to give you access to its derived types' members.
An interface specifies a contract against which you can code. You don't code against a specific implementation but the interface, so the interface has to provide all the members that you'd like to access. In your case this would result in width and height beeing part of ColliderV2 instead of BoxColliderV2. However this defeates the logic you are trying to mimic.
There are a few approaches that you can take:
Either make your collider type a variant, like
using ColliderType = std::variant<BoxColliderV2, MyOtherCollider, ...>;
and check for the actual type when you want to access the member
ColliderType myCollider = /* generate */;
if (auto boxCollider = std::get_if<BoxColliderV2>(&myCollider); boxCollider)
boxCollider->width = 0;
Or, keep the base class that you have, remove the operator-> and the template and do a dynamic cast on it:
ColliderV2* col = new BoxColliderV2;
if (auto boxCollider = dynamic_cast<BoxColliderV2*>(col); boxCollider)
boxCollider->width = 0;
You can also hide details like width or height behind more generic functions that are part of the interface. For example:
class ColliderV2 {
public:
virtual void setBounds(float width, float height) = 0;
};
class BoxColliderV2 : public ColliderV2 {
public:
void setBounds(float width, float height) override {
this->width = width;
this->height = height;
}
private:
float width;
float height;
};
int main()
{
ColliderV2* col = new BoxColliderV2;
col->setBounds(1, 1);
}
What you are trying to do is discouraged by C++. What you are trying to do is to change the type of something based on the return value of a function. The type system is designed to stop you from writing code like this.
One important restriction of a function is that can only return one type-of-thing. You can return one of a list of things if you wrap those possibilities in a class, and return that. In C++17, a ready-made class for this is std::variant. The restriction on this is that the list of things must be fixed (or a closed-set). If you want an arbitrary set of return values (open-set), you must use a different approach. You must restate your problem in terms a function that is done on the return value.
class BoxColliderV2 : public MyBaseCollider {
public:
void SetWidth(float new_width) override;
};
You may find this video useful. The bit of interest starts at around 40 minutes (but watch the whole video if you can). If you are interested in advice, I would suggest starting with std::variant, and if it works, move to virtual functions. Problems like collision detection get really complicated really quickly, and you will almost certainly require double dispatch at some stage. Start simple, because it's only going to get more complicated.
These excerpts from the ISO-Guidelines may help
1. When you change the semantic meaning of an operator, you make it
harder for other programmers to understand you code. guideline.
2. Dynamic casting is verbose and ugly, but deliberately so, because dynamic casting is dangerous, and should stand out. guideline
I think you are approaching the problem from the wrong direction. The purpose of an interface is that you don't have to know about the exact type or the implementation.
For example: You are using Axis-Aligned Bounding Boxes for collision detection. So, even if your CircleCollider uses a radius, you are still able to calculate its width and height from it. Now, you don't have to worry about if you are dealing with a BoxCollider or a CircleCollider, you have everything to make a Bounding Box.
class Collider
{
public:
virtual float x() const = 0;
virtual float y() const = 0;
virtual float width() const = 0;
virtual float height() const = 0;
};
class BoxCollider : public Collider
{
// Implementation...
};
class CircleCollider : public Collider
{
// Implementation...
};
Of course, you are maybe using something else, and not AABBs. I just wanted to demonstrate how you can use interfaces effectively.
I'm trying to build a ray tracer. I have a class called Shape which I'm extending to the class of Sphere (and other shapes as well, like triangles). Shape has the method
virtual bool intersect(Ray) =0;
And so I create the Sphere class by
class Sphere : public Shape{
public:
Sphere(){};
bool intersect(Ray){/*code*/};
};
I have a main class which I use to create a list of Shape pointers. I create a Sphere pointer and do the following:
Sphere* sph = &Sphere();
shapes.emplace_front(sph); //shapes is a list of Shape pointers
Then when I want to trace the ray in another class I do the following:
for (std::list<Shape*>::iterator iter=shapes.begin(); iter != shapes.end(); ++iter) {
Shape* s = *iter;
bool hit = (*s).intersect(ray);
}
But I get the error that I cannot call intersect on a the virtual class Shape, even though it should be that *s points to a Sphere type object. What am I doing wrong with inheritance?
One problem is this:
Sphere *sph = &Sphere();
It creates a temporary object of type Sphere, stores a pointer to that temporary, then destroys the temporary. The result is nonsense.
Change it to this:
Sphere *sph = new Sphere();
things will work much better.
I'm making the design of a OO framework and I'm facing the following problem.
Let's say that in the framework I have a Shape interface and the users are free to implement and extends (adding new functions) the Shape interface to create their own figures, e.g. Square and Circle. To make these new objects available the users have to register them into a ShapeFactory specifying the name of the shape (string) and the object.
Furthermore, the framework provides an interface called ShapeWorker which defines the following function:
class ShapeWorker
{
public:
void processShape( Shape& shape ) = 0;
};
The users are free to implement the ShapeWorker interface to make specific shape worker, e.g. SquareWorker and CircleWorker. To make these new objects available the users have to register them into a WorkerFactory, specifying the name of shape (string) and the object.
At a certain point, the framework, given a string representing the shape's name, creates a new Shape, by using the ShapeFactory, and afterwards (somewhere else in the code) creates a new ShapeWorker, by using the WorkerFactory with the same shape's name. The processShape is then called providing the Shape instance created before.
[ ... ]
Shape* myShape = shapeFactory.create( shapeName );
[ ... ]
ShapeWorker* myWorker = workerFactory.create( shapeName );
myWorker->processShape( *myShape );
[ ... ]
The point is that, doing so, I force the user implementing, for example, the SquareWorker to make a down-cast from Shape to Square into the processShape function so to access to the full Square's interface:
class SquareWorker
{
public:
void processShape( Shape& shape )
{
Square& square = dynamic_cast< Square& >( shape );
// using Square interface
}
};
This is against the Liskov substitution principle.
Now, is this approach wrong? What would it be the better solution? Note that I don't want to implement the processShape as Shape's member function.
I hope the description has been clear enough.
Thanks in advance for your help.
Simo
Unless your shapes have a common interface that must be used by the workers, this approach seems fully correct to me. A shape worker is more or less specialized on a specific shape, thus has knowledge about the class it handles. It would be nicer to do that using a common interface for all shapes but you cannot put everything you would need into it, it would end up fully cluttered. Downcasting is a correct mean to solve this.
The use of templates could help you out: you could create a base class for all workers
template <class T>
class BaseShapeWorker : ShapeWorker
{
public:
void processShape( Shape& shape )
{
T& specificShape = dynamic_cast< T& >( shape );
processShape( specificShape )
}
protected:
virtual void processShape( T& shape ) = 0;
};
This would not need the implementers to know about this downcast and ease the implementation by maybe also providing some often reused functionality.