A lack of C++ encapulation makes me feel guilty

Started by
24 comments, last by Barn Door 21 years, 7 months ago
Hi, I''m writing 3D asteroids. I''ve got a class managing the asteroids and a class managing the energy pulses projected from the ship. Obviously, when the pulses strike the asteroids there is a collision. In order to check for a collision, there needs to be a comparison of the data in one object with the data in the other. The book I''ve been reading suggests that you have each object own a pointer to the data in the other object. This seems quite sensible. The thought of using accessor functions to get hold of a copy of some data which comprises a list of asteroids seems ridiculously inefficient. However, given that two distinct objects have direct access to some of each other''s data, surely this is a big contradiction of encapsulation? How is one supposed to write good C++ given this situation? Thanks, BD.
Advertisement
Accessor methods are very necessary for encapsilation. It might seem inefficient now, but when you start writing much more complex applications, your going to kick yourself if you don't use them.

You should ahve a "world" amanger class that keeps track of all the positions of all the objects. It should do the collision checking by using the accessors of the coordinates and sizes of all the objects. Just do a brute force comparison of every object to every object for now, you can optimize it later.

Ponters between objects like this is not good. Better to have an integer reference to each object. Like asteroid "1", "2", "3", "4", "5", etc. Then pulse "1", "2", "3", "4", "5". The "world" manager can then use store the pointers in an array and index them using the int reference.

Ok, did that make sense? I think I lost myself.

[edited by - smanches on August 28, 2002 3:44:31 PM]
Stephen ManchesterSenior Technical LeadVirtual Media Vision, Inc.stephen@virtualmediavision.com(310) 930-7349
How about have the energy-pulse-controller simply ask the astroid-controller to do the collision detection and pass it the information about the location/size/orientation of the pulse. The astroid-controller then goes through its own list and if it finds a collision it takes care of splitting/destroying the affected astroid. It then returns to the energy-pulse-controller weather or not a collision occurred.

The key here is decoupling the objects. Instead of the energy-pulse-controller having to sift through all the astroids - and thus be aware of how the astroids are stored - it relies on the astroid-controller. Now the implementaion of the astroid-controller can change without affecting the energy-pulse-controller class.

** timeout expired **

[edited by - wayfarerx on August 28, 2002 3:45:14 PM]
"There is no reason good should not triumph at least as often as evil. The triumph of anything is a matter of organization. If there are such things as angels, I hope that they're organized along the lines of the mafia." -Kurt Vonnegut
Wow! Wasn''t expecting that response.

WayfarerX. You seem to be suggesting that in order to compare the data, one manager object should pass its data, one element at a time, to the other within the parameters of a function.

I think this is, in fact, sort of what the book I''m reading is doing. One object still has access to the other''s elements but it only uses them to call a collision check function on each of them and sends the necessary data in the parameter list.

What you are saying seems better than this because in your version, the calling object would only need a pointer to the other manager and not its data. The called manager is obviously capable of iterating through its own list of objects.

Alas, invoking countless function calls and copying data to the stack seems to be the way to do things!

Smanches. Are you saying that all the data should be kept in the world manager?

Cheers,

BD.




What if the data one manager requires from the other is quite a substantial object in its self?

Surely, copying by value larger and larger objects will sooner or later become too inefficient?

If giving one object access to another''s data is so bad, why do so many of the DirectX interface function parameter lists take pointers to structures like D3DXVECTOR3s?

BD.
You should pass pointers to methods. Then use the pointers to access the accessor methods of the object. Just remember, whatever class created the pointer is the only class that should destroy it.

I was just sugesting you use a single manager to manager all the objects. Have one list of Asteroids and one list of Pulses. The way WayfarerX describes would work also. The only difference is that the pulse manager would own the collision detection. Is that the correct place for it in the long run?

If your application grows and you soon have other objects that need collision detection, the pulse manager would start doing a lot of stuff that really isn''t it''s job. It''s the pulse manager, not the collision detector.

Just trying to explain the bigger picture. You can do what WayfarerX says without any problems.
Stephen ManchesterSenior Technical LeadVirtual Media Vision, Inc.stephen@virtualmediavision.com(310) 930-7349
quote:Original post by Barn Door
Alas, invoking countless function calls and copying data to the stack seems to be the way to do things!


Don't focus on optimization up-front. Passing simple geometric constructs consisting of only a couple of numbers is practically free when you're only doing it once per frame. You can also pass arguements as const refrences. Works the same as a pointer but safer.

quote:Original post by smanches
I was just sugesting you use a single manager to manager all the objects. Have one list of Asteroids and one list of Pulses. The way WayfarerX describes would work also. The only difference is that the pulse manager would own the collision detection. Is that the correct place for it in the long run?


Actually, I would pull the actual collision detection code out into another object that could be used by any of the managers, or anyone else for that matter. Using a global object manger would work, but I'm generally against applying global solutions to simple concepts like Astroids. The ship, the energy pulses, and the astroids have very little in common when it comes to how they act and interact within the simulation. Splitting them up allows you to focus just on the cases where they interact.

[edited by - wayfarerx on August 28, 2002 6:11:03 PM]
"There is no reason good should not triumph at least as often as evil. The triumph of anything is a matter of organization. If there are such things as angels, I hope that they're organized along the lines of the mafia." -Kurt Vonnegut
quote:Original post by Barn Door
What if the data one manager requires from the other is quite a substantial object in its self?

Beware of any one class trying to do too much.
quote:
Surely, copying by value larger and larger objects will sooner or later become too inefficient?

Whoa, hold those horsies! Why is anything being copied by value? Objects can be passed by reference through the manager for collision detection--no need for object copies at all.

Whenever you have interdependent functionality, like you do here between pulses and asteroids, you should look at doing an escalation: make a superclass that has the functions that will do this functionality. This is straight out of Lakos'' "Large Scale C++ Design", except his example uses Windows and Rectangles:

  class CollisionUtil{  // returns true if collision happened  static bool hasCollided (const Pulse &p, const Asteroid &a);};class Game{  std::list<Pulse> m_pulseList;  std::list<Asteroid> m_astList;  void loop ()  {    // ...    // Collision detection loop    for ( /* all pulses p */ )    {      for ( /* all asteroids a */ )      {        if (hasCollided (p, a))        {          doBigAsteroidExplosion (p, a); // or whatever        }      }    }    // ...  }};  

That''s the general idea. Note that when you use references, the compiler interally passes object pointers--not object copies. It''s very fast.

BTW, this presumes that a collision can be detected purely via the public members of pulse & asteroid.

It''s also important to note that this design allows Pulse to not know anything about Asteroid, and vice versa. This is part of the reusability theme. If you write a kick-butt Asteroid class, and you want to use it in a future game, you don''t want to forced to have to also use Pulse as well, do you? This design will free you of that dependency.
Thanks guys.

This thread seems to have split into two. On one hand we have..

Given two sets of independent data which need to be compared, where does the comparison take place?

and ...

How does one object gain access to another object's internal data?


I like the idea of a generic collision detecter. I could overload a 'hasCollided' type function to handle any combination of collisions required.

quote:
Objects can be passed by reference through the manager for collision detection


This is still confusing me. If an object has a pointer or a reference to another object's internal data then doesn't that break encapsulation even if they're a const pointer or reference?
Surely for strict encapulation all values would have to be copied by value?

Once again, why is it acceptable for DirectX interfaces to ask for non-const pointers to your program's data in their parameter lists?

Cheers,

BD.

[edited by - Barn Door on August 28, 2002 9:08:18 PM]

[edited by - Barn Door on August 28, 2002 9:10:25 PM]
dude, lay off the religion. Really, you have heard about the benefits of a certain style and guess what? Your mind is locked into it. Now there are benefits, don''t get me wrong, but there are downsides too. You have to mix and match styles to get a good program. Again, adherance to a religious style of programming isn''t helping you. Afterall, without your religion wouldn''t you have solved this problem by now? Go finish your program now. Then afterwards you can reflect on what went wrong and look around for new techniques.

This topic is closed to new replies.

Advertisement