07 September 2013

Constantly correcting const correctness

I made a comment on Shamus Young's blog, a blog far nicer than mine which you should visit right now. He actually posts frequently! In this case, the subject of Software Engineering came up, and how it differs from the kind of programming that goes on in the trenches. Anyway, my comment ended up so lengthy that it ought to be a post unto itself. Here it is.

It can be hard to pin down these nebulous software development roles. As a subject, Software Engineering bored the hell out of me at Uni. It was all flowcharts and entity relationship diagrams and that jazz. Databases were thrown in there at one point because hey, databases.

The actual Software Engineering as a thing we need to do before putting code to screen is, well, something I just figured was part of the Good Programmer role. But I must admit, I didn't really appreciate the benefits of e.g. const-correctness until working on a very large project with other people, much later.

For the still-sane:-

const is a keyword in C++ and friends that (amongst other things) lets the programmer declare that this thing here won't be modified. While this doesn't really matter for the simple stuff like integers, which are passed by value, a lot of the really interesting stuff happens by passing references to objects around. If I declared my function as
Frobbable *
frobnicate(int a, string b, Frobbable *thing_to_frob);
then it might not be immediately obvious what happens to the Frobbable thing I pass in as the third argument; does it get modified, and the return value of the function is merely for convenience? Or does the function just make a new copy and return that? Changing the signature to
Frobbable *
frobnicate(int a, string b, const Frobbable *thing_to_frob);
is a guarantee from the author of the frobnicate function that no harm will come to the original thing_to_frob, leading us to believe that any changed versions will come back via the return value.

(Note, I wouldn't be passing raw pointers around, this is just an example, most likely it'd be better to use smart pointers declared as Frobbable::ptr_type and Frobbable::const_ptr_type, or turn the whole class into a pImpl-idiom thing, possibly with copy-on-write semantics... and then you see just how far the rabbit-hole goes)

The other thing adding const does for us is ensure that when we're writing the frobnicate function, we really don't accidentally modify the object when we know we shouldn't be. If the Frobnicate class declares some methods, some of which change the internal state of a Frobnicate object and some of which don't, we can (and should) add const to the method to indicate that fact:-
class Frobnicate
{
public:

   string 
   get_name() const; // doesn't change our internal state.

   void
   set_name(string new_name); // might.

private:
   string d_name;
}
Now if we accidentally were to use the set_name method inside our frobnicate function, the compiler will throw a fit at us, yelling at us that we promised we weren't going to change thing_to_frob, we promised, why would you do that. When we have a const Frobnicate object, the only methods we can call on it are those that have themselves been declared const.

This is where the real madness sets in. If you haven't been designing with const in mind from the beginning, it is an absolute nightmare to go back to all those methods and check if they should be const or not, just to gain this benefit of maybe having the compiler catch a few bugs for you. It's not possible to merely make a few objects const, because then the methods you use on those objects need to be const too, and anything they interface with will also need to be const, and so on. In a small project, I must admit I don't bother with it. Let everything be mutable, we're probably going to rip this code up tomorrow anyway. But in anything large, it can be very useful to prevent you from introducing side effects to your code. Suddenly you realise that yes, you really shouldn't be modifying this object at this time, this needs to be a wholly separate code path, this is why you've been getting that strange glitch.