In computer science, const-correctness is the form of program correctness that deals with the proper declaration of objects as mutable or immutable. The term is mostly used in a C or C++ context, and takes its name from the const keyword in those languages. The idea of const-ness does not imply that the variable as it is stored in the computer's memory is unwriteable. Rather, const-ness is a compile-time construct that indicates what a programmer may do, not necessarily what he or she can do. In addition, a class method can be declared as const, indicating that calling that method does not change the object. Such const methods can only call other const methods but cannot assign member variables. (In C++, a member variable can be declared as mutable, indicating that a const method can change its value. Mutable member variables can be used for caching and reference counting, where the logical meaning of the object is unchanged, but the object is not physically constant since its bitwise representation may change.) In C++, all data types, including those defined by the user, can be declared const, and all objects should be unless they need to be modified. Such proactive use of const makes values "easier to understand, track, and reason about," and thus, it increases the readability and comprehensibility of code and makes working in teams and maintaining code simpler because it communicates something about a value's intended use. For simple data types, applying the const qualifier is straightforward. It can go on either side of the type for historical reasons (that is, const char foo = 'a'; is equivalent to char const foo = 'a';). On some implementations, using const on both sides of the type (for instance, const char const) generates a warning but not an error. For pointer and reference types, the syntax is slightly more subtle. A pointer object can be declared as a const pointer or a pointer to a const object (or both). A const pointer cannot be reassigned to point to a different object from the one it is initially assigned, but it can be used to modify the object that it points to (called the "pointee"). (Reference variables are thus an alternate syntax for const pointers.) A pointer to a const object, on the other hand, can be reassigned to point to another object of the same type or of a convertible type, but it cannot be used to modify any object. A const pointer to a const object can also be declared and can neither be used to modify the pointee nor be reassigned to point to another object. The following code illustrates these subtleties: void Foo( int * ptr, int const * ptrToConst, int * const constPtr, int const * const constPtrToConst ) { *ptr = 0; // OK: modifies the pointee ptr = 0; // OK: modifies the pointer *ptrToConst = 0; // Error! Cannot modify the pointee ptrToConst = 0; // OK: modifies the pointer *constPtr = 0; // OK: modifies the pointee constPtr = 0; // Error! Cannot modify the pointer *constPtrToConst = 0; // Error! Cannot modify the pointee constPtrToConst = 0; // Error! Cannot modify the pointer To render the syntax for pointers more comprehensible, a rule of thumb is to read the declaration from right to left. Thus, everything before the star can be identified as the pointee type and everything to after are the pointer properties. (For instance, in our example above, constPtrToConst can be read as a const pointer that refers to a const int.) References follow similar rules. A declaration of a const reference is redundant since references can never be made to refer to another object: int i = 42; int const & refToConst = i; // OK int & const constRef = i; // Error the "const" is redundant Even more complicated declarations can result when using multidimensional arrays and references (or pointers) to pointers. Generally speaking, these should be avoided or replaced with higher level structures because they are confusing and prone to error.
Chat with our AI personalities
There are four ways to declare a pointer:
<type> * <name>;
- a variable pointer to a variable type.
const <type> * <name>;
- a variable pointer to a constant type.
<type> * const <name> {address};
- a constant pointer to a variable type.
const <type> * const <name> {address};
- a constant pointer to a constant type.
The last two declarations are constant pointers. Being constants, you must assign an address to them at the point of instantiation.
Note that the 2nd declaration is a pointer to a constant, not a constant pointer. That is, the object being referred to is treated as if it were constant but the pointer itself is variable so we can still change the address, but not the value at that address.
int const *p declares a 'p' pointer, that points to a constant integer
Every non-static member function has a hidden pointer parameter named this which refers to the instance of the class the function was invoked against. For a given class, C, the type of the hidden this pointer is const C* but if the function is declared const, the pointer is const C* const. When referring to any class member, m, from within any non-static member function, this->m is implied.
//Array Passer //Demonstrates relationship between pointers and arrays #include <iostream> using namespace std; void increase(int* const array, const int NUM_ELEMENTS); void display(const int* const array, const int NUM_ELEMENTS); int main() { cout << "Creating an array of high scores.\n\n"; const int NUM_SCORES = 3; int highScores[NUM_SCORES] = {5000, 3500, 2700}; cout << "Displaying scores using array name as a constant pointer.\n"; cout << *highScores << endl; cout << *(highScores + 1) << endl; cout << *(highScores + 2) << "\n\n"; cout << "Increasing scores by passing array as a constant pointer.\n\n"; increase(highScores, NUM_SCORES); cout << "Displaying scores by passing array as a constant pointer to a constant.\n"; display(highScores, NUM_SCORES); return 0; } void increase(int* const array, const int NUM_ELEMENTS) { for (int i = 0; i < NUM_ELEMENTS; ++i) array [i] += 500; } void display(const int* const array, const int NUM_ELEMENTS) { for (int i = 0; i < NUM_ELEMENTS; ++i) cout << array[i] << endl; }
An array's name is not a constant pointer; it's not even a pointer! An array name is a reference to the array itself. Unlike pointers, references have no memory of their own, therefore a reference cannot be constant (only what it refers to can be constant). A reference is nothing more than an alias for a memory address. Since there is no separate storage for references, you cannot reassign references while they remain in scope. So, in that sense, it behaves like a constant pointer. But a pointer is a variable, so even if declared const, it requires memory of its own in order to store the memory address it points to.Example:int a[10];int * const p = a; // const pointerassert( *p &a ); would cause an assertion. Likewise, assert( p != &p); proves that p must reside in a separate memory address from that referred to by a. It has to: pointers are variables even when they are declared const.
A constant member function is an instance method that can only modify the mutable members of the class instance. This is achieved by implicitly declaring the this pointer constant. The this pointer is a hidden parameter that is implicitly passed to every instance function, and that points to the current instance of the class. Static member functions do not have an implicit this pointer and therefore cannot be declared const. Consider the following simple class that has both mutable and non-mutable member variables, and both constant and nonconstant member functions: struct foo { void constant()const; void nonconstant(); private: int data1; mutable int data2; }; void foo::constant()const { data1++; // not permitted: the implicit this pointer is declared const data2++; // ok: data2 is mutable } void foo::nonconstant() { data1++; // ok: the implicit this pointer is declared non-const data2++; // ok: data2 is mutable } Note that data2 can always be modified since it is declared mutable, but data1 can only be modified by nonconstant member functions. Members should only be declared mutable when they are only used internally by a class (such as when locking a mutex for thread safety), or where a value needs to be calculated and cached the first time it is accessed.