Introduction
This article describes basic concepts of C++ const and volatile type qualifiers.
Type Qualifiers
- Type specifiers indicate the type of the object or function being declared.
- Type Qualifiers add refinement to the type of the object or function being declared.
- C++ recognizes const and volatile type qualifiers.
Const Type Qualifiers
- C const qualifier explicitly declares a data object as something that cannot be changed
- A const object or variable must be initialized and its value is set at initialization.
<![CDATA[ class X{ int a; }
int main()
{
const X obj;
const int f;
}
- In case of class object, all the member variables must be initialized in the constructor. A default user defined constructor will initialize all the values to random value.
<![CDATA[ class X{ X(){};
void print(){};
int a;
}
int main()
{
const X obj;
const int f=1;
}
- When object is defined as constant, no data members of class can be modified, or any non-constant member function cannot be called.
- You cannot use const data objects in expressions requiring a modifiable lvalue, i.e., left side of assignment.
<![CDATA[ class X
{
X(){};
void print(){}; int a;
}
int main()
{
const X obj;
const int f=1;
f=2;
obj.a=2;
obj.print();
}
- An object that is declared const is guaranteed to remain constant for its lifetime, not throughout the entire execution of the program. And thus const variables cannot be used in place of constant or in a constant expression. The const variable k is const for the lifetime of the function, it is initialized to value of j at the start of the function and remains a const till the function returns only.
<![CDATA[ void foo(int j)
{
const int k = j;
int ary[k];
}
- A const variable has internal linkage by default, storage qualifiers need to be specified explicitly. Const Objects can be used in header files, since they have internal linkages and can be used instead of \#define for constant values;
- For a const pointer, you must put the keyword between the * and the identifier. In this case, the value of pointer/address cannot be changed but the value the pointer is pointing to can be changed.
<![CDATA[ int c=0;
int * const y=&c;
int a=10,b=20;
*y=a;
cout << *y << ":" << y << endl;
*y=b; cout << *y << ":" << y << endl;
<![CDATA[ int c=0;
int * const y=&c;
int a=10,b=20;
y=&a;
- To define a pointer to a const value, the keyword must be placed before the type specifier.
<![CDATA[ int c=0;
const int * y=&c;
int a=10,b=20; y=&a;
cout << *y << ":" << &c <<":" <<
y << endl; y=&b; cout <<
*y << ":" << &c <<":" <<
y << endl;
a=30; cout <<
*y << ":" << &c <<":" <<
y << endl;
We can change the values of a,b,c and since in the last line pointer points to address of b, we can deference the pointer and obtain the value of new variable.
- We can see that the pointer can be changed, however changing the value the pointer y points gives a error. It is a pointer to a const integer.
Any expression of the form $*y=1$ will give an error. We cannot change the value of the variable via pointer.
<![CDATA[ int c=0;
const int * y=&c;
*y=1;
- In the below expression, neither the value pointed to or the pointer can be changed
<![CDATA[ const int * const ptr ;
- Declaring a member function as const means that the this pointer is a pointer to a const object. \\ There are reasons that you will want the compiler to prevent a member function from being able to change any
private
member variables of the calling objects. \\ - The data member of class will be constant within that function.
<![CDATA[ class X
{
int x;
public: X():x(100){};
void print() const{ x=1;
};
- A const member function cannot call any non-const member function.
<![CDATA[ class X
{
int x;
public: X():x(100),a2(200){a1=&x;};
void print2 (){ }
void print()
const{ print2();
}
};
int main()
{ X obj; obj.print(); }
- If an object of class is delared as const, it can only call const functions of the class, but not non-const functions of the class. Declaring the object as const means that this pointer is a pointer to a const object.
<![CDATA[ class X
{ int x; public: X():x(100){};
void print2 (){ } void print() const{ } };
int main()
{ const X obj;
obj.print();
obj.print2();
- If a function is defined as const, we can still change the value using
const_cast
. A better approach would be to declare the variable desired to be changed as mutable.
<![CDATA[ class X
{
int x;
public: X():x(100){};
void print() const{ const_cast <int>(x)=1;
};
- The mutable storage class specifier is used only on a class data member to make it modifiable even though the member is part of an object declared as const or function is declared as const.
<![CDATA[ class X
{ mutable int x;
public: X():x(100){};
void print() const{ x=1;
int main() { const X obj; obj.print(); }
};
- Pointer to constant data can be used as function parameters to prevent the function from modifying a parameter passed through a pointer or passed as a reference.
<![CDATA[ class X
{ public: X(){}
void print(const int * x2,const int & x1)
{ *x2=1;
x1=1;
- The member function can also return a const value or reference to const value. A return of an object invokes a copy constructor while returning a reference does not invoke a copy constructor. Thus for efficiency it can be used. However the return by reference supplies the local address of a variable, not if the main object has been deallocated, the value which address points to is undefined. \\ The reference should be to an object that exists.
<![CDATA[ class X
{ public: X(){}
const int &print
(const int * x2,const int & x1)
{ int x3=1;
cerr << x3 <<":" << &x3 << endl;
return x3;
}
};
int main()
{
X *obj=new X();
int x=0;
const int &
x4=obj->print(&x,x);
cerr <<
x4 <<":" << &x4 << endl;
delete obj; cerr << x4 <<":" << &x4 << endl;
}
We can see that value pointed by x4
is difference after the object has been deallocated. This is inherent drawback of call by reference, where user must ensure that the object is not being referenced.
- However, in case of applications like streaming camera frames, sensor data, etc., we do not want to allocate a new address for every new frame of data. In this case, returning a constant reference or pointer provides an efficient way of providing the data without copying it or allocating it everytime.
<![CDATA[ class X
{
int x3;
public: X():x3(0){}
const int &get()
{ x3=x3+1; return x3; }
};
int main()
{
X *obj=new X();
int x=0;
for(int i=0;i<2;i++)
{ const int &
x4=obj->get();
cerr << x4 <<":" << &x4 << endl;
}
Volatile Type Qualifier
- The volatile qualifier maintains consistency of memory access to data objects.
- Volatile objects are read from memory each time their value is needed, and written back to memory each time they are changed.
- The volatile qualifier declares a data object that can have its value changed in ways outside the control or detection of the compiler and the compiler is thereby notified not to apply certain optimizations to code referring to the object.
- When applied to a class, all members of the class have the same type qualifiers.
- An item can be both volatile and const. In this case, the item is modified by some asynchronous process.
- You can define or declare any function to return a pointer to a volatile or const function.
- You can declare or define a volatile or const function only if it is a nonstatic member function.