Stroustrup says that you should use structs unless you can specify an invariant for the data structure. I have to represent a physical object which holds things such as position, velocity, mass and inertia (and so on). I also want to render the object, for which I will need setup in the constructor and unloading stuff in the destructor. So I guess that’s an invariant, but it feels wrong to put all this physics stuff into it that doesn’t really have invariants.
The drawing parts are pretty trivial, so it doesn’t feel worth it to separate them from the rigid body in this case.
Any advice?
edit: I suppose I just realized the answer to my own question. Rigid bodies do have invariants, namely that it has to follow the physical laws.
2
When Stroustrup is talking about invariants, he is not in the first place talking about the rules that apply to the real world objects that our data structures represent, but about the rules that are required to safeguard your design.
For instance, if you have a flat array of objects, that should be kept sorted because your class assumes the list is sorted when doing binary search lookups, the fact that the list is sorted is an invariant that is logically guarded by the class.
In order to make sure your class will always work correctly, you can make sure nobody outside the class has direct access to the list, and that inserts/removals keep the list sorted.
If you make a struct in C++, by default all fields are exposed as public. If you have constraints on how the fields are modified, such a struct can be sabotaged: For instance, a struct that has a pointer to a refcounted object, should not expose it’s pointer field to the outside: Users of such structs could set the pointer to null without decreasing it’s refcount, or vice versa, causing memory leaks or corrupted memory.
A lot of time, I choose invariants for my own convenience: For instance, I might choose that a View
always has non-null pointer to a valid Model
. I can ensure this by preventing that the Model
property on the view can not be removed from the outside, and I add a check to the constructor that the Model
passed in is non-null, and I throw an exception otherwise. Then I can be sure that I always have a valid Model
in my View
, so I never need to handle the case that the Model
pointer in the view is null
.
Back to your example: Invariants can be thought of as assumptions that the code depends on elsewhere. So if your calculations assume a non-negative mass, you can guard this requirement by not letting users of your object class set the mass directly, and check all values passed in.
It could very well be that your code will happily work with negative mass objects, except that they will behave weirdly. Then guarding this aspect at the object level may not add any real value, so you could consider leaving it out.
You could of course separate your concerns.
Hold position, velocity, mass, etc in a struct and create another class such as NewtonianObject which contains an instance of your struct and applies Newtonian physics to the values. This would give you the freedom to later create a NonNewtonianObject that applies a different set of physical laws to the same type of data.
Whilst a struct is just a class with all public members, semantically I tend to think of a struct as an aggregation of data, maybe with some constructors to enable the data to be initialised easily and provide default values.
Once you start getting “actions” on your data I usually think it is best to use a class, but actually in recent years I have sometimes created a struct of the data members, then used a class wrapper that has a private instance of the struct, so the data is still encapsulated, and performs the functions.
Doing this also makes it easy to store and retrieve the data without having to make the classes that do this into friends whilst giving direct access to data members.
You could well do something like this in your case too.