1.17.  const* and *const

[ fromfile: constptrs.xml id: constptrs ]

Suppose that you have a pointer ptr storing the address of a variable vbl:

Type* ptr = &vbl; 

When using a pointer, two objects are involved: the pointer itself and the object pointed to. That means there are three possible layers of protection that can be imposed with const.

  1. If you want to make sure that ptr cannot point to any other memory location (i.e., cannot store a different address), you can write it one of two ways:

    Type* const ptr = &vbl;
    Type* const ptr(&vbl);
    

    The pointer is a const but the addressed object can be changed.

  2. If you want to make sure that the value of vbl cannot be changed by dereferencing ptr, you can write it in two ways:

    const Type* ptr = &vbl;
    const Type* ptr(&vbl);
    

    In this case, the addressed object is a constant but the pointer is not.

  3. If you want to impose both kinds of protection you can write:

    const Type* const ptr = &vbl;
    const Type* const ptr(&vbl);
    

Here is a good way to remember which is which: Read each of the following definitions from right to left (starting with the defined variable).

const char* x = &p;        /* x is a pointer to const char  */
char* const y = &q;        /* y is a const pointer to char  */
const char* const z = &r;  /* z is a const pointer to a const char  */

Example 1.33 demonstrates the two kinds of protection.

Example 1.33. src/constptr/constptr.cpp

#include <QTextStream>

int main() {
    QTextStream cout(stdout);
    int m1(11), m2(13);
    const int* n1(&m1);
    int* const n2(&m2);
    // First snapshot
    cout << "n1 = " << n1 << '\t' << *n1 << '\n'
         << "n2 = " << n2 << '\t' << *n2 << endl;
    n1 = &m2;
    //*n1 = 15; 1
    m1 = 17;    2
    //n2 = &m1; 3
    *n2 = 16;   4
    // Second snapshot
    cout << "n1 = " << n1 << '\t' << *n1 << '\n' 
         << "n2 = " << n2 << '\t' << *n2 << endl;
    return 0;
}
Output:

src/constptr> ./constptr
n1 = 0xbffff504 11
n2 = 0xbffff500 13
n1 = 0xbffff500 16
n2 = 0xbffff500 16
src/constptr>



1

Error: assignment of read-only location

2

m2 is an ordinary int variable, okay to assign

3

Error: assignment of read-only variable 'n2'

4

Okay to change target

<include src="src/constptr/constptr.cpp" href="src/constptr/constptr.cpp" id="constptrcpp" mode="cpp"/>


Figure 1.5 shows two snapshots of memory at the noted spots in Example 1.33, to help clarify what is happening when the program runs. Notice that the program produces a memory leak.

Figure 1.5.  Memory Snapshots

Memory Snapshots
Memory Snapshots

An object that is read-only when accessed through one pointer may be changeable when accessed through another pointer. This fact is commonly exploited in the design of functions.

char* strcpy(char* dst, const char* src); // strcpy cannot change *src

It is okay to assign the address of a variable to a pointer to const. It is an error to assign the address of a const object to an unrestricted (i.e., non-const) pointer variable because that would allow the const object's value to be changed.

int a = 1;
const int c = 2;
const int* p1 = &c;   // okay
const int* p2 = &a;   // okay
int* p3 = &c;         // error
*p3 = 5;              // error

It is good programming practice to use const to protect pointer and reference parameters which do not need to be altered by the action of a function. Read-only reference parameters provide the power and efficiency of pass-by-reference with the safety of pass-by-value (Section 5.5).