Developer

C++: One keyword for three constructs

The const keyword in C++ can do more than declare constant variables. It can also declare pointers and member functions. We'll walk you through the code for pointers, member functions, and objects.


You're probably familiar with the concept of constants and the keyword const that declares variables as constants. Yet const does more than declare constants. It can declare const pointers and const member functions. In this article, I will show how to distinguish between these constructs, explain their uses, and discuss the semantics of const objects.

Const declarations
Const declarations can be confusing, as they use a single keyword to refer to three distinct constructs. Let’s examine each of these constructs closely.

Const objects
When you declare an object as const (here the term object is used in its wider sense, i.e., a piece of memory in which a variable or a class object is stored), you ensure that the program doesn’t modify it. A const definition must include a proper initializer. Listing A contains various instances of const objects.

Const pointers
Declaring a pointer as const ensures that the programmer cannot assign a new address to it thereafter. The object bound to that pointer is still modifiable, as shown in Listing B.

Const member functions
A class object’s state consists of the values of its nonstatic data members. A member function that doesn’t change its object’s state should be declared const. You do that by appending const after its parameter list. For example:
class Person{
public:
 int getAge() const {return age;} //const member function
private:
 int age;
};


Note that const member functions are an important constituent of the Design by Contract idiom: They ensure that what the programmer promises to do (i.e., leave the object’s state intact) is enforced by the compiler. An attempt to modify a data member from inside a const member will trigger a compilation error:
 
int Person::getAge() const
{
 return ++age; // compilation error
}



In addition, the const documents the function’s behavior.

Compound types
So far, so good. Alas, troubles start with compound declarations. Can you tell what the types of x and y are?
const char * x= “hello”;
char const * y= “world”;


Both x and y have the same type, “pointer to const char,” in spite of the variation in the position of const. The keyword const can appear before or after a typename without changing the declaration’s meaning, much like the keywords long and unsigned in the following example:
 
int long x;
long int y;
unsigned long l;
long unsigned m;

 

Now consider the following const declaration:
char * const z= “world”;

Here, the type of z is “const pointer to non-const char.” How does the compiler know that z is a const pointer, whereas x and y are not?

Distinguishing between const pointers and pointers to const variables?
By checking whether the const appears before or after the asterisk, you can tell a const pointer from a const variable. The sequence “* const” indicates a const pointer; by contrast, if const appears before the asterisk, the object bound to the pointer is const.

Const pointers of const objects
You may combine const objects and const pointers in a single declaration. The resulting type is a const pointer to a const object. For example:
const int n=10;
const int * const p= & n; // const pointer to const int


You can tell the p is a const pointer because its declaration contains the sequence * const. It points to const int because the declaration contains another const before the asterisk. The use of const pointers to const objects is particularly useful in applications that access ROM devices through a memory buffer that has a fixed address.

At this point, you’re probably wondering if you're allowed to combine the three types of const in a single declaration. The answer is yes. In the following example, we declare a const member function that returns a const pointer to const int:
 
class A{
//…
const int * const get_scores(int id) const;
};

Operator const_cast<>
The const_cast<> operator removes the const quality of an object. There are, however, several restrictions on the use of this operator, as we will see shortly.

Removing the const qualifier
Although const_cast<> can remove the const qualifier of an object, this doesn't mean that you can modify the resulting object. Consider the following example:
 
const char * p= “hello world”;
int main() {
 char * s = const_cast <char *> (p); // get rid of const
 strcpy(s, “danger!”); // bad; undefined behavior
}
 

Trying to overwrite the string s will result in undefined behavior. The problem is that const objects may be stored in the system’s read-only memory. A brute-force removal of const allows you to treat the object as if it weren’t const (e.g., passing it to a function that takes char *) but not to modify it.

Non-const to const conversion
Most programmers don’t know that const_cast<> performs the opposite operation as well; namely, it can convert a non-const object to const. For example:
char s[]= “fasten your seatbelt”;
size=strlen(const_cast<const char *> (s));// more explicit


But you will rarely see such uses of const_cast<>h. The reason is that C++ automatically performs non-const to const conversions in a context that requires const. Therefore, you never truly need use const_cast<> for that unless you want to document your code.

Conclusion
The syntactic barrier often causes programmers to give up the use of const, thereby compromising their programs’ quality and producing code that is less readable and maintainable. Admittedly, the difference between pointers to const objects and const pointers isn’t as distinct as should be, especially when dealing with complex declarations. However, checking the position of the const qualifier with respect to the asterisk should resolve the ambiguity, and const member functions are easily recognized by the presence of const after the parameter list.

 

Editor's Picks