C/C++: Why we go for const-correctness (Rant)
Posted: Tue Nov 28, 2017 3:47 am
Far too often I encounter source, or people writing source, steeped in a mindset of "works for me, what are you complaining about".
Because I have a beautiful example on my desk right now, allow me to rant and vent a bit from the position of a maintenance coder.
You know what Const Correctness means? It means that everything that could be "const" qualified, should be "const" qualified.
This is about more than mere pedantry.
Let's say I have a C++ libray before me. Unfortunately C++ is rather peculiar when it comes to cross-compiler compatibility, in a way that C isn't. For a library that is about performing a service (as opposed to a C++ framework, like e.g. Boost), it is beneficial to provide a C interface. This way, it does not matter for your client(s) which compiler you use, or which compiler they use.
It's a shame this hasn't been done with this particular library from the get-go, but that is not the point.
In general, wrapping a C++ ABI in C is not much of a problem. Where a vector or string object is expected, you write a wrapper taking an array (with an additional "count" parameter where needed). Where some object is returned to be used with subsequent functions, you wrap it in a void-pointer typedef. It's all quite straightforward.
The problems start when the C++ interface takes pointers or references to non-const data objects. In my case, a vector< int >. As you are writing the wrapper C functions, you have to ask yourself, "do I need to un-wrap this parameter again to make potential changes visible to the caller, or has someone just forgotten a 'const'?"
So you add the 'const' to the C++ function and run the compiler to see if there are any complaints.
And you get errors from one level down the call hierarchies about "binding 'const std::vector<int>' to reference of type 'std::vector<int>&' discards qualifiers."
So you add 'const' to those functions' parameters.
And get the same errors from the next level down the call hierarchies.
And pretty soon you have touched half the source files of the project, just to ensure that this one parameter from your external API is, indeed, used read-only. One hell of a changeset for a mere wrapper function. Half an hour of pouring through units and modules that are not your responsibility, upsetting several co-workers that have to merge your changes, and generally disturbed productivity.
And then you repeat the whole process for the next parameter / function...
If the original author had gone for const-correctness, writing the wrapper should have been a no-brainer, five-minute exercise.
As with so many Clean Coding exercises, what is dead-easy to do while you're writing the code, becomes a nuisance to retrofit. The same goes for e.g. unit testing, automated source formatting, running code checkers like Valgrind, or even merely using a one-touch build system. All things that bring immediate benefits for comparatively little extra effort... if you model your project that way from the get-go.
Don't postpone Clean Coding to "later". Later never comes. Write clean code from the get-go.
Because I have a beautiful example on my desk right now, allow me to rant and vent a bit from the position of a maintenance coder.
You know what Const Correctness means? It means that everything that could be "const" qualified, should be "const" qualified.
This is about more than mere pedantry.
Let's say I have a C++ libray before me. Unfortunately C++ is rather peculiar when it comes to cross-compiler compatibility, in a way that C isn't. For a library that is about performing a service (as opposed to a C++ framework, like e.g. Boost), it is beneficial to provide a C interface. This way, it does not matter for your client(s) which compiler you use, or which compiler they use.
It's a shame this hasn't been done with this particular library from the get-go, but that is not the point.
In general, wrapping a C++ ABI in C is not much of a problem. Where a vector or string object is expected, you write a wrapper taking an array (with an additional "count" parameter where needed). Where some object is returned to be used with subsequent functions, you wrap it in a void-pointer typedef. It's all quite straightforward.
The problems start when the C++ interface takes pointers or references to non-const data objects. In my case, a vector< int >. As you are writing the wrapper C functions, you have to ask yourself, "do I need to un-wrap this parameter again to make potential changes visible to the caller, or has someone just forgotten a 'const'?"
So you add the 'const' to the C++ function and run the compiler to see if there are any complaints.
And you get errors from one level down the call hierarchies about "binding 'const std::vector<int>' to reference of type 'std::vector<int>&' discards qualifiers."
So you add 'const' to those functions' parameters.
And get the same errors from the next level down the call hierarchies.
And pretty soon you have touched half the source files of the project, just to ensure that this one parameter from your external API is, indeed, used read-only. One hell of a changeset for a mere wrapper function. Half an hour of pouring through units and modules that are not your responsibility, upsetting several co-workers that have to merge your changes, and generally disturbed productivity.
And then you repeat the whole process for the next parameter / function...
If the original author had gone for const-correctness, writing the wrapper should have been a no-brainer, five-minute exercise.
As with so many Clean Coding exercises, what is dead-easy to do while you're writing the code, becomes a nuisance to retrofit. The same goes for e.g. unit testing, automated source formatting, running code checkers like Valgrind, or even merely using a one-touch build system. All things that bring immediate benefits for comparatively little extra effort... if you model your project that way from the get-go.
Don't postpone Clean Coding to "later". Later never comes. Write clean code from the get-go.