0

I am making a simple 2D physics engine for circles and squares. I have a body class that I want to be given a unique_ptr instance of a shape when created. The shape can either be a circle or a box. The body is the object that I want to throw into my game loop to check for collisions. When checking for collisions, I am interested in the data associated with the shapes. A very primitive example is shown below:

#include <iostream> #include <memory> struct Shape { virtual float calculateArea() const = 0; }; struct Circle: public Shape { const float m_radius; Circle(float radius) : m_radius(radius) {} float calculateArea() const {return m_radius*m_radius*3.14;} }; struct Square: public Shape { const float m_side_length; Square(float side_length) : m_side_length(side_length) {} float calculateArea() const {return m_side_length*m_side_length;} }; struct Body { Body(std::unique_ptr<Shape>& shape): m_shape(std::move(shape)) {} std::unique_ptr<Shape> m_shape; }; int main() { std::unique_ptr<Shape> circ = std::make_unique<Circle>(5.0); std::unique_ptr<Shape> square = std::make_unique<Square>(10.0); std::shared_ptr<Body> body1 = std::make_shared<Body>(circ); std::shared_ptr<Body> body2 = std::make_shared<Body>(square); } 

Essentially, I am wondering if there is a way to access the radius of a shape or the side length of a shape. I can see that something like

body1->shape->m_radius or body2->shape->m_side_length doesn't work but I really want to find a solution where my shapes can be grouped into a general shape class and I'm still able to access their unique features through the body class. This is for collision detection btw.

I saw some related posts such as here: Access members of derived class through base class pointer C++ but the overall problem is slightly different from mine and the question is quite old.

Thanks for the help - hope it makes sense!

7
  • 1
    Read up a bit more on virtual functions. A base class knows nothing about any derived classes, and cannot as a new derived class can be added years after the base class was last compiled. But if it contains virtual functions, the derived classes can override those functions and take more specific actions, operating on its member variables and perhaps returning them. Commented Sep 22, 2023 at 20:12
  • Yeah I have a virtual function in the example here - the only issue is that the derived classes may have different quantities of data. I'm sure I can figure out a workaround. Commented Sep 22, 2023 at 20:17
  • In most cases you expand the interface of the base class with virtual function to handle all the cases where specific knowledge is required without the user needing to have any of that specific knowledge. When you have cases where you cannot do this, inheritance is probably not the right solution. See the Liskov Substitution Principle for a tool to help determine when inheritance is a bad idea even when it might look like a good idea on the surface. Commented Sep 22, 2023 at 20:18
  • In this specific case, it makes no sense for a shape to have a radius because most shape-derived classes will not have a radius. If you need to get this information from a shape, inheritance is not the right tool, at least not at shape level. Also see the example of the rectangle vs the square in the Liskov link given above. Commented Sep 22, 2023 at 20:23
  • It works the other way around, child classes can know things of their parent classes not the other way around. Also using inheritance for drawing shapes it not all that nice either. Watch this : Breaking dependencies from cppcon. It shows stuff about drawing. TLDR whatever you think inheritance usually isn't the solution Commented Sep 22, 2023 at 20:23

1 Answer 1

0

You said

I am wondering if there is a way to access the radius of a shape or the side length of a shape.

Yes, there are ways of getting derived class members but I don't recommend taking that route to solve your programming problem. It can get hairy pretty quickly.

Let's say there are N sub-classes of shape. Given two shape objects, if collision detection logic depends on the object types, you will need NC2 functions to cover the functionality.

One way to do this would be to have a registry of collision detection logic classes/functions. You can add entries to the registry when your program starts. You will need to add code to look up the registry for a class/function for two object types, and then call the functions. In the class/function that implements the detection logic, it's fair to use dynamic_cast to get the specific object types and get the necessary data from the specific object types.

Here's an outline of what that entails.

class CollisionDetector { public: virtual bool detectCollision( shape const& shape1, shape const& shape2) const; }; class CollisionDetectorRegistry { public: using CollisionDectorBuilder = CollisionDetector*(void); static void registerCollisionDetector( std::string const& shapeType1, std::string const& shapeType2, CollisionDectorBuilder builder); static CollisionDetector* getCollisionDetector( shape const& shape1, shape const& shape2); }; class CircleCircleCollisionDetector : public CollisionDetector { public: // It is fair to dynamic_cast shape1 and shape2 to circle // In the function. virtual bool detectCollision( shape const& shape1, shape const& shape2) const; static CollisionDetector* build(); } class CircleSquareCollisionDetector : public CollisionDetector { public: // It is fair to dynamic_cast shape1 and shape2 to circle/square // In the function. virtual bool detectCollision( shape const& shape1, shape const& shape2) const; static CollisionDetector* build(); } // Add the following in some place that is executed at start up time. CollisionDetectorRegistry::registerCollisionDetector( "circle", "circle", CircleCircleCollisionDetector::build); CollisionDetectorRegistry::registerCollisionDetector( "circle", "square", CircleSquareCollisionDetector::build); // Shape-independent function for detecting collision bool detectCollision( shape const& shape1, shape const& shape2) { CollisionDetector* detector = CollisionDetectorRegistry::getCollisionDetector(shape1, shape2); if ( detector == nullptr ) { // Throw an execption. } else { return detector->detectCollision(shape1, shape2); } } 

I realize that you'll need to fill some gaps but I am assuming you can do that.

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for this thorough answer. You mentioned that this method can "get very hairy quicky". Could you lay out an approach where this can be minimized? I will definitely using your example as guidance.
@bboan Could you lay out an approach where this can be minimized? I thought I did that in my answer.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.