Hi,
following the discussion on using const, I have written o short doc
about it. See attached file.
I know the idea was to create a wiki page, but it would be better if
this takes the heat on ML first.

jan
Using const qualifier


Variables
Const means that access to that variable won't modify the data represented by 
the variable. Constness applies to the type to the left of the const keyword. 
If there is none it applies to the one on the right. For simple types types it 
means that:

   const int foo = 0;

is equivalent to:
   
   int const foo = 0;

you can even use:
   const int const foo = 0;

but that does not make much sense.

It gets interesting when const is added to pointer declarations:

const char *foo = "foo" declares variable pointer to constant char data. Thus 
this:

         const char *foo = "FOO";
         foo = "BAR";
is perfectly OK, while
         foo[0] = 'B';
produces error: assignment of read-only location ‘*foo’.

The same error:
         char const *foo = "FOO";
         foo = "BAR";
         foo[0] = 'B'; /* error: assignment of read-only location ‘*foo’ */

and even this one:
         const char const *foo = "FOO";
         foo = "BAR";
         foo[0] = 'B'; /* error: assignment of read-only location ‘*foo’ */

Different sitaution is with const pointer to variable data:
         char *const foo = "FOO";
         foo[0] = 'B'; /* This is OK */
         foo = "BAR";  /* error: assignment of read-only variable ‘foo’*/ 

The basic rule is ALWAYS DECLARE VARIABLES CONST IF YOU DON'T NEED TO MODIFY 
THE DATA. This greatly improves readability of the code. If you see a variable 
you just need to go to it's declaration (most IDEs know how to do this with one 
click/keyboard shortcut), if the variable is const you know its value without 
scanning all the code in between. Example:

int init_foo()
{
   /* 100+ lines of preparing data */

   const int rc = register_new_foo();

   if (rc != EOK) {
      /* 100+ lines of cleanup */
   }
   return rc;
}

You don't need to read the cleanup code because it won't modify the return 
value.
Even if the cleanup looks something like this:
         if (rc != EOK) {
                  int local_rc = cleanup_x();
                  if (local_rc != EOK) {
                     return local_rc;
                  }
                  ...
                  local_rc = cleanup_y();
                  ...
                  return local_rc;
         }

Finding returns and going from return line (or any other use) to variable 
declaration is fast and easy. It helps if declaration provides as much 
information as possible. Using const where possible removes the ambiguity of 
variable declarations. It establishes the following rule: const => variable is 
not modified in the code, non-const => variable IS modified somewhere in the 
code.



Function parameters and return values.

There is no parameter pass/return by reference, all parameters are copies. It 
makes little sense declaring them const because they can't be changed by design.
         int foo(int bar);
It makes no sense decalring
         const int foo_c(const int bar).
There is no way foo can modify what is passed to it.

Return values are only ever read before destruction and storing them to result 
variable (or ignoring) so it makes little sense to declare them const.
 GCC warns about using const on return values.

      DECLARING PARAMETERS AND RETURN VALUES CONST IS OF LITTLE USE.

It exposes function implementation info to the header, but it can be beneficial 
in the same way as local variables. If your code (mis)uses parameters as local 
variables.

Using pointers is a bit different. It still makes no sense to declare pointer 
parameters constant, but USING POINTERS TO CONST DATA IS OF GREAT USE. Example:

int count_something(const struct foo_t *instance);

const struct foo_t *my_foo = get_foo();
const int something_count = count_something(my_foo);

If the data in foo_t changed after a call to this function it's a bug (memory 
corruption, ...).

If the data in my_foo are modified after calling:
int count_something_else(struct foo_t *instance);
there is no information. It can be either bug or programmer's brain damage 
(modifying data in something named count_*).

It's easy to look for explicit casts when someone needs to modify data 
referenced to by const pointer. Therefore:

      ALWAYS PASS POINTER TO CONST DATA UNLESS THE FUNCTION ACTUALLY MODIFIES 
THE DATA.

There is one problem though: locks and reference counts. If these obects are 
part of the structure then even RO access needs to modify parts of the 
structure and pointers to const data cannot be used.
_______________________________________________
HelenOS-devel mailing list
[email protected]
http://lists.modry.cz/cgi-bin/listinfo/helenos-devel

Reply via email to