Saturday, November 14. 2009Const Correctness
What is const correctness?
It's a programming paradigm that helps writing correct code. In C++, const correctness comprises a set of different techniques, you can read up about them here. In this article however I only want to focus on one form of const correctness, that is object constness. Why should I care about const correctness? Because it increases type safety, makes your code more easy to understand, and it helps making your code correct. Const objects can be seen as invariants in mathematical terms, that is, objects that do not change, that do not vary. An example in code:
In this example, myNumber is a const object (an int is really a POD type, but let that not distract us). This indicates that I cannot change the object. And indeed, if I were to try that, the compiler would complain about it, which is a good thing. It helps preventing mistakes by accidentally changing this object later on. Additionally, it helps readability, because anyone can clearly see that myNumber is not meant to be changed. So you see, writing that "const" before it is a little bit of work, but it has a big pay-off. I think it's worth it, so I try to use it always in my code. PS: It's likely that somewhere in this article I made a slight mistake or a bad wording. With these things, you will always find a C++ expert who will point out something, because C++ is very pointoutable. So that might happen, but I'm fairly sure I got the gist right Trackbacks
Trackback specific URI for this entry
No Trackbacks
Comments
Display comments as
(Linear | Threaded)
I've seen a couple of KDE developers committing things like "remove redundant consts" in code like
void someFunction(const int someArg, const int someArg2) making it void someFunction(int someArg, int someArg2) which I found really odd.
If it's in the declaration, it's indeed useless since your ints are passed by value.
However, it's useful in the definition to indicate the const-ness of those local variables. in the header: void foo(int); in the cpp: void foo(const int i) { ... } is allowed and indeed encouraged.
I wouldn't use different signatures in headers and the implementation. Some compilers/linkers will err out because of that.
No they won't. Not in this case, since the args are passed by value. The "const" in the definition is here only to mark the local variable as const. The signature of the function is actually unchanged.
They will. See http://blogs.fsfe.org/adridg/?p=423 for another one pointing out the same thing, or https://bugs.kde.org/show_bug.cgi?id=213528 for an example where it actually failed.
I already replied over there that the compiler is wrong. Which is sad. But maybe the comment didn't go through ?
Anyway, here it is: ----- See the C++98 standard (e.g. here: http://www.kuzbass.ru:8086/docs/isocpp/decl.html) 8.3.5 - Functions [dcl.fct] ” -3- A single name can be used for several different functions in a single scope; this is function overloading (clause over). All declarations for a function with a given parameter list shall agree exactly both in the type of the value returned and in the number and type of parameters; the presence or absence of the ellipsis is considered part of the function type. The type of a function is determined using the following rules. The type of each parameter is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or “function returning T” is adjusted to be “pointer to T” or “pointer to function returning T,” respectively. After producing the list of parameter types, several transformations take place upon these types to determine the function type. Any cv-qualifier modifying a parameter type is deleted. [Example: the type void(*)(const int) becomes void(*)(int) --- end example] Such cv-qualifiers affect only the definition of the parameter within the body of the function; they do not affect the function type. If a storage-class-specifier modifies a parameter type, the specifier is deleted. [Example: register char* becomes char* --- end example] Such storage-class-specifiers affect only the definition of the parameter within the body of the function; they do not affect the function type. The resulting list of transformed parameter types is the function’s parameter type list. ” Emphasis on “Such storage-class-specifiers affect only the definition of the parameter within the body of the function; they do not affect the function type.” -----
It's even in the FAQ for the compiler:
http://developers.sun.com/prodtech/cc/documentation/ss11/mr/READMEs/c++.html#cNameMangling
Agreed.
Even if it didn't cause compiler issues, it would still be confusing, and it would be very bad style. I don't see a reason for pulling such stunts.
Yes, the instances I saw were where the const's were originally in the definition (I should have specified originally; sorry).
"Qualifying the arguments with const is not very useful if the arguments are not pointers or references. The only possible use that I can see is to get a compile error if you 'accidentally' (a typo, for example, or using = instead of ==) modified the variables holding the arguments passed." Then Mark's example of const int myNumber = 41; must also be "not very useful".
"Then Mark's example of
const int myNumber = 41; must also be "not very useful"." Not quite the same. Having compile-time constants declared const is different from function arguments declared const. Local variables (including such compile-time constants) are specified as const to protect against accidental modification and to let the compiler make an easier decision about optimization. const for function arguments is very important for reference/pointer types (where the object being pointed to is declared const - 'const char p' and not 'char const p'). Having const for value arguments is 'not very useful' compared to this.
Qualifying the arguments with const is not very useful if the arguments are not pointers or references. The only possible use that I can see is to get a compile error if you 'accidentally' (a typo, for example, or using = instead of ==) modified the variables holding the arguments passed.
My problem with trying to use const is that I always end up wanting to call a function with a non-const object and then the compiler complains.
void DataLogger::setLogName(const QString path); then when I call that from somewhere, like: logger->setLogName(args.at(i+1)); It won't compile, because that QStringlist will return a non-const QString. So usually I don't bother because I don't feel like putting const casts all over the place.
That doesn't make any sense.
You can pass a non-const object as const argument to a function/method. You can't do the opposite though. Maybe that's what you meant.
That seems very strange to me. You should normally always be able to promote a non-const to a const, even implicitly.
The converse is not true; I recently had that problem, trying to pass a const as an input parameter to a function whose prototype didn't mark it as const. Using const in function prototypes is not entirely redundant - even on types passed by value - as the caller is clearly shown which parameters are to be treated as read-only input, a feature that is otherwise missing from C and C++. Steve
It's interesting that you wrote this, simply because it hits home: Basically you were illustrating why many programmers don't use const correctness. While your example possibly isn't correct, it shows that your basic problem is a design issue, which is exactly what const correctness can cure.
Before creating a method or an object, think about its purpose: "Does it modify things, or will it just query information and return this?" Giving this some thought is actually a good thing, it makes your design decisions better.
A toplevel const in a non-defining function declaration for a value-parameter is totally redundant.
Not because it is a value parameter (well, you can still make the parameter const in the function definition!), but because the constness of the parameter (which is local to the function definition) has no effect on the caller. Showing it "which parameters are treated as read-only input" within the function body is totally useless to the caller - it's got the same information as saying "the function body contains a for-loop and one local variable": it's an implementation detail. It's bad style to include a const in a non-defining declaration, because it exposes implementation details. On a broken compiler, i would rather remove both consts than including the const into the header. Consequently, the C++ Standard specifies that both types "void(int)" and "void(int const)", are the same (the toplevel const is ignored for determining type equality). Compilers that fail to link a function because a const is missing in a non-defining declaration contain a bug. Just my two cents. Please also read http://stackoverflow.com/questions/1554750/c-const-keyword-use-liberally/1554762#1554762 .
I see myself using the const modifier all over the place. Sometimes I wonder if my code would look nicer if variables were const by default (which is the case in functional programming languages, BTW). So when I decide that a variable should really be re-assignable, I'd use something like a "variable" modifier.
What I really miss in C++ is the ability to declare the loop variable of a for statement as const. Sure, declaring it as const should only have an effect on the loop's body. The benefit would be to know immediately that it's value is only changed in the for loop's header, which is good practice anyway. Unfortunately, there is no way (that I know of) to express this in C++. The obvious workaround would be to move the loop's body into an extra method, but who would want to do that? Both points remind me that functional programmig sometimes has its benefits in terms of expressiveness and that the concept of loops is really broken.
I wrote a section in my C++ Pitfalls thinger a while back on const-correctness which might be interesting to readers of this article:
http://developer.kde.org/~wheeler/cpp-pitfalls.html#const
Thanks Wheels, that's a good page. I knew about it from back in time, but had forgotten that it existed.
Informative and interesting stuff. |
Amarok LinksCalendar
QuicksearchCategoriesSyndicate This BlogBlog Administration |
|||||||||||||||||||||||||||||||||||||||||||||||||

