Tuesday, 1 February 2011

Const-ness of variables - usage

“Using read only memory is a long history in computers!! You shouldn’t allow some array of memory to be written to ensure that, the program behaves properly. In a old ROM hardware, you can only hard erase and reprogram. But latest ROMs, allow Programmable erase and reprogram. So, nothing is const nowadays!! :). So, even in C++, you cannot understand what is really a const and what is not!! :P”

Metaphor

   As a general meaning of constant in mathematics, a constant can never change once it gets a value. The constant value will usually be a initial fixed value which exists through out the life time of a mathematical equation. Its the same in computers as well once a constant is set, its set forever and can never be manipulated. In computers, as you cannot change some device addresses or ports, these addresses/ports goes into a const variable.

Concept

   Both C and C++ allow const variables. But only with C++, const for objects came into picture. Where does people use const?

  • To declare a global constant value specific to a file. It replaces unnecessary #define usage for constant values. (even in headers, you can keep a const variable without linker errors)
  • To control the arguments and return values of a function. Marking an argument or return value as constant proves to the caller that the function doesn’t modify the values passed to or from it.
  • In C++, with introduction of classes, its possible to mark a member function as constant. :) This passes the object to const functions in a read only format.

After the C++ concept of passing object data read-only came, there came the need for two types of const-ness

  • Physical const-ness: This comes by default with the property of const, that no one can modify the data physical through any means. Mostly, physical constness is over a object or variable. If its done forcefully still, then it is equivalent to writing into a ROM memory which leads to crash!!
  • Logical const-ness: This type of const-ness comes with pointers or references. Even if a data pointed to by a const pointer or reference cannot be changed!! But note that, this is just a logical const enforced by compiler. This can be violated unlike physical const. That’s why more protected data has double const. Even the data pointed to by pointers will be const which means, its physically const.

The important point here is: if data is const, then it is a pure physical const-ness which can never be modified. If a pointer or reference becomes const, then it is just logical const-ness enforced by compiler since they can point to any object and only the address they point to is const.

How do the violate a logical const over pointers and reference? You can never change the address stored by them since they are physical const but you can change the data hold by them indirectly!!.. This is a raw example:

   1:  void main()
   2:  {
   3:     int a = 10;
   4:     int b = 20;
   5:     const int& c = a;
   6:     // This will give error, through c you cannot modify a 
   7:     c = b;   
   8:     // But a is not following physical const, overwrite!!
   9:     a = b; 
  10:     std::cout<<c; //you will get 20, since a is modified!!
  11:   
  12:  }

The above code explains about how logical const-ness is violated. c being a constant alias, cannot be used to change a’s value. But a being a non-const can directly get changed which in turn makes c’s value as well to change. :)

More on Logical const-ness violations

   As you can see const is very strict and confusing if mis-used. So, many initial and novice C++ developers avoid const. But const is the best practise which give smiles on the face of your code future maintainer!!! At least even if he is a psychopath, he won’t be too much angry if you have used const at least at some spots on a large code base :)

  To make it more eased on a larger code base, C++ gave new ways to break the const-ness as explained above. But as the above code, its directly breakable but most of the cases, constant references/pointers run across the code and suddenly at some spot, you need to modify the a reference or pointer holds. In that case, you cannot access the main variable.

  For doing this, C++ provides 2 mechanisms: const_cast and mutable data member. Both of these when used in code are noted as a biggest bad practices in a C++ design. :) But when it is necessary for logical const-ness of object, i.e, some data of the object when modified doesn’t break the const-ness level of the object, then const_cast/mutable is used. mutable is better than const_cast since we can mark particular member variables which can be modified in a const object. const_cast removes the constness altogether which means you can access everything in the object if the object is fully non-const.

Important points to remember

  • We started with const member functions to explain logical const-ness. But left it hanging. Because, people should understand that every member function gets a this pointer as first argument to it. :) So, when the “this” pointer is const, its a const member function. So, the below code is ok!!

 

   1:  class TestConst
   2:  {
   3:      int a;
   4:   
   5:  public:
   6:      TestConst() { a = 0;}
   7:   
   8:      int ret() const { return a; }
   9:   
  10:      int ret() { a=10; return a; }
  11:   
  12:      int normalApi() { a=20; return a; }
  13:   
  14:  };
  15:   
  16:  void main()
  17:  {
  18:      TestConst obj; 
  19:      const TestConst& objRef = obj;  
  20:      // only const ret() can be called, a=0 
  21:      std::cout<<objRef.ret();
  22:      // logical const, non-const ret() is called
  23:      std::cout<<obj.ret();  
  24:      //now, a=10 !!.. same physical const problem
  25:      std::cout<<objRef.ret();  
  26:      // throws an error, pointer is const!!
  27:      std::cout<<objRef.normalApi(); 
  28:  }
  • There are many hidden things in a C++ object which is why people never understand such concepts correctly!! When it comes to virtual function, there are lot more things hidden!!