My Stuff
Username

Password

Articles/Snippets
ASP Classic
C
C#
C++
CSS
HTML
Java
Javascript
MySQL
Perl
PHP
Python
Ruby
Unix/Linux/BSD
VB 6
VB.NET


Keyword: const (modifier)


No votes yet

Revision 3

Preface

This is my second tutorial for codenewbie.com. Unlike my first tutorial for this site (Abstract: What are object? ), I've
decided to use words as litle as possible. Instead, short but powerfull statements, accompanied with lots of
simple code snippets are offered this time. This way I hope the student doesn't need to re-read lenghty explanations.
Working with code is what the programmer needs I think.

Prerequisites:

  • Basic knowledge and handling of classes, like object instantiating, calling (class) member methods through an object.
  • Basic knowledge and handling of pointers and references, like passing objects by reference.
  • Basic knowledge and handling of simple arrays.

When you see #include then it means that you can copy and paste the code straight into your IDE to run it.

Tutorial starts here.

const declares variable to have a constant value. The value of the variable can not be changed.

const int x = 67;
x = 30; //Illegal
x++; //Illegal
int y = x; //OK, Y can be changed now, x cannot be changed.
const int z = y// OK, but z cannot be changed from now on.

A const variable must be initialized.

const in x; //Illegal, must initialize a value here.
x =  57; // Illegal, too late for value assignment. 

Constant values are to be treated like normal values.
- Therefore, the same scoping rules apply.

int x;
cout<<"enter number: "<<endl;
cin>>x;
const int SIZE = x; //OK
cout<<SIZE<<endl; //OK
cout<<SIZE*SIZE<<endl; //OK...
int SomeIntArray[]; //... but illegal for array size.
int SomeIntArray[]=5; //OK, the one and only way for constant size arrays.

Constant size arrays must have an initialized size at compile time:

  • The size of an array is determined by the keyword "sizeof()".
  • sizeoff() returns a value of type "size_t".
  • size_t is evaluated at compile time. In the code snippet above, SIZE is evaluated at run time.
  • The actual value of size_t is treated as a constant.This is called a compile time constant.
  • Arrays with a constant size must be determined at compile time. The compiler needs to be able to generate a compile time constant.
  • Consequence: you must initialize the size of a const array at compile time.

Initialization IS NOT the same as assignment.

float a; //Declaration, preparing for assignment.
a = 12.49; //Assignment.
 
float x = 12.49; //Initializer. x is determined at compile time.
 
float z;
cin>>z;
float y = z; //Initializer, y is determined at run time.
 
int q;
cin>>q;
const int SIZE = q;//Initializer, SIZE is determined at <span style="font-style:italic">run time</span>. You can use it but not for const array-sizing.
const int SIZE = 5;//Initializer, SIZE is determined at <span style="font-style:italic">compile time</span>

Constant size array. The right way.

const int SIZE = 57; //Initializing, SIZE determined at compile time.
int SomeArray[]; //OK

Remember, when your code compiles, the actual const size of an array MUST be known.

Pointers can be constants too. Just like values.
- Pointer can point to an object.
- And both can be constants. The pointer (wich holds an adress), and the actual object (wich the pointer points to) can be a constant.

Three combinations are possible:

char* const name = "McGregor";
name++; //Illegal. Increasing adress with 1 is not allowed."name" is here the constant.
*name = 'a;' //OK, when adress is dereferenced.

const char* name = "McGregor";
name ++ //OK, the adress is NOT the constant.
*name = 'a'; //Illegal, dereferenced pointer (*name)is the constant.

const char* const name = "McGregor";
name ++ //Illegal, pointer is a constant and...
*name = 'a'; //...llegal, dereferenced pointer (*name)is a constant too.

Wrap it up:
This will be important when passing values through (member) methods.

T* const => Lets you change the object, NOT the adress of the object.
const T* => Lets you change the adress of the object, NOT the object it self.
or
Close to pointer =>adress can not be changed.
Close to type =>contents can not be changed.

You may always treat a NON-const value as a const.

#include <iostream.h>
 
int main(int argc, char* argv[])
{
	int a = 15;
	const int b = a;
 
	cout<<b<<endl;
 
	return 0;
}

You can NOT treat a const value as a non-const...
#include <iostream.h>
int main(int argc, char* argv[])
{
	int a = 15;
	const int b = a;
 
	b=a+1; //Illegal, lvalue is const, rvalue is not a const.
 
	cout<<b<<endl; //This won't be executed due to a compiler error.
 
	return 0;
}

...unless you use a static_cast...
#include <iostream.h>
int main(int argc, char* argv[])
{
	int a = 15;
	const int b = a;
 
	static_cast<int> (b)=a+1; //OK
 
	cout<<b<<endl; //Output is 16.
 
	return 0;
}

... or a const_cast.
#include <iostream.h>
 
void ProcessAddition(const int& data, int extradata);
 
int main(int argc, char* argv[])
{
	int a = 15;
	const int b = a;
 
	ProcessAddition(b,a); 
	cout<<b<<endl; //Ok, const-ness removed from b. Output: 30
 
	return 0;
}
 
void ProcessAddition(const int& data, int extradata)
{	
	int& ref_temp = const_cast<int&>(data); //Remove const-ness of data...
	ref_temp+=extradata; //... then modify data!
}

Question:
Is this a healthy way of thinking, safety and object-oriented wise?
Are there other considerations than safety and object orientation?

A possible answer will be given. Think first.

Finding a specific element in an unsorted array.
Code below uses a straight forward algorithm:

#include <iostream.h>
 
//"const int*" assures the contents of the original array can't be changed.
bool ProcessSearch(const int* intArray, int arraySize, int elementToFind); 
 
int main(int argc, char* argv[])
{
	int SomeArray[10] = {1, 9, 1, 7, 1, 4, 7, 2, 3, 4};
	int intFindThis; //Holds the element we would like to find.
 
	cout<<"Enter the number to be found: "; //Try 5, then try 7
	cin>>intFindThis;
 
	int found = ProcessSearch(SomeArray, 10, intFindThis);
 
	cout<<found<<endl; //1 if found, 0 if not found.
 
	return 0;
}
 
/*This implementation of ProcessSearch won't even attempt to change the original contents of the array.
Nevertheless, we've implemented "const int* intArray" anyway, just to make sure. This is called 
robustness! */
bool ProcessSearch(const int* intArray, int arraySize, int elementToFind)
{	
	for (int k=0; k<arraySize; k++)
	{
		if (intArray[k]==elementToFind)
		{
			return true;
		}
	}
	return false;
}

We can optimize the algorithm: Faster for large arrays.
The previous function ProcessSearch() needed to see IF the sought element exists.
The new function presented below will iterate the array too, but it will replace the last element of the array with the
value we are looking for. This way we can skip the "IF" part, because we are certain that the sought value
exists. Because the function doesn't use "IF" anymore in the loop, the function executes faster.
Imagine an array of 1200 elements. With this function we saved ourselves 1200 "if-then" tests!

The original last element is stored in a local temporary variable, by the way. This temporary variable
is compared (with the sought value) when all the other elements did not contain the sought value. The replaced
last element does not count obviously. It is only used to avoid the if-then statement (it's a trick remember?).

#include <iostream.h>
 
//"const int*" assures the contents of the original array can't be changed.
bool ProcessSearch(const int* intArray, int arraySize, int elementToFind);
 
int main(int argc, char* argv[])
{
	const int SomeArray[10] = {1, 9, 1, 7, 1, 4, 7, 2, 3, 4}; //const array. Values of elements can not be changed.
	int intFindThis;
 
	cout<<"Enter the number to be found: "; //Try 7 then 59...
	cin>>intFindThis;
 
	int found = ProcessSearch(SomeArray, 10, intFindThis);
 
	cout<<"Search result (1=found, 0=not found: "<<found<<endl;
	//Test to check that original array is restored after changing.
	cout<<"Value of last element: "<<SomeArray[9]<<endl; 
 
	return 0;
}
 
bool ProcessSearch(const int* intArray, int arraySize, int elementToFind)
{
	int last_entry = intArray[arraySize-1];
    	int* refArray = const_cast<int*>(intArray); //Casting away the "security" of the original array!
	//Changing the contents of the original array through a new pointer!
    	refArray[arraySize-1] = elementToFind; 
 
	//Verify that array IS changed despite the constness declared everywhere.
	cout<<"New element value at last position: "<<intArray[9]<<endl; 
 
    	int i=0; //Start checking element [0] first, wich holds the first value.
    	while ( intArray[i++]!=elementToFind ); //Is it in this element? 
 
	//Restoring the (original) last element of the array.
    	refArray[arraySize-1] = last_entry; 
	//Did while loop find a match OR is it maybe the last (original) element?
    	return i < arraySize || last_entry == elementToFind;
}

A possible answer to the question asked:
Casting away the const-ness of an object gives us the opportunity to implement neat algorithms.
But extreme caution is in it's place. The contents of the object might get corrupted. Perhaps
by human fatigue, or by another programmer (in a team based development strategy) who didn't notice what was going on.
Removing the constness of any object means that we can't rely anymore on the "const" keyword. We would like
to expect that a const is a const. Period. But with casting, this idea has become very fragile.
In the end, the programmer, and the programmer alone, is responsible for Run-time Type Information (RTTI).

What you see is NOT what you get.
- Look at the code below. It is what you see...

void PrintInteger(int* i); //Watch it, "int* i" is NOT a constant.
PrintInteger(i + 15); //ERROR or WARNING. Why?

- ... but the code below is what you get from the compiler:
void PrintInteger(const int* i);
const int* TMP@ = i + 15;
PrintInteger(TMP@); //"TMP@" is a const reference. It is passed to a non-const argument! This creates problems.

const object, protection.
- Execute the code below. A print funtion is to print stuff, not to change stuff...
#include <iostream.h>
 
class SomeClass
{
private:
	int m_intImportantNumber;
public:
	void Print() 
	{
		cout<<"Correct output: "<<m_intImportantNumber<<endl;
		m_intImportantNumber = 200; //This is not what we had in mind...
 
		//Look at the consequences of this mistake:
		cout<<"New member value (changed by mistake): "<<m_intImportantNumber<<endl;
	}
 
	void SetImpNum(int i)
	{
		m_intImportantNumber = i;
	}
};
 
int main(int argc, char* argv[])
{
	SomeClass obj;
	obj.SetImpNum(12);
	obj.Print();
 
	return 0;
}
[/code]
 
- We can protect ourselves (and the contents of an instantce of a class) against these mistakes:
[cpp]
#include <iostream.h>
 
class SomeClass
{
private:
	int m_intImportantNumber;
public:
	void Print() const
	{
		cout<<"Correct output: "<<m_intImportantNumber<<endl;
		m_intImportantNumber = 200; //ERROR. m_intImportantNumber is considered a const for the Print() method.
 
		//This line will never be executed because the compile complains.
		cout<<"New member value (changed by mistake): "<<m_intImportantNumber<<endl;
	}
 
	void SetImpNum(int i)
	{
		m_intImportantNumber = i;
	}
};
 
int main(int argc, char* argv[])
{
	SomeClass obj;
	obj.SetImpNum(12);
	obj.Print();
 
	return 0;
}

could you find the difference?
Look at the code below carefully. It is very often used.
void Print() const //"const" keyword protects calling object from changing at the wrong moment(s).
{ ... }

This is the generalization of the code to protect calling objects: T function() const

Mutable.
- Our fast array search program we previously made, needed to cast a const away to use a trick.
- What if you need to "un-const" certain objects/variables in a class (to perform more tricks for example)?
- Use the keyword "mutable".

Make sure you run this program so it becomes clear what happens!

#include <iostream.h>
 
class SomeClass
{
private:
	 int m_intImportantNumber;
	 mutable double m_dblOtherNumber; //mutable used here.
public:
	void Print() const
	{
		cout<<"Message generated by Print() const BEFORE changing double:"<<endl;
		cout<<"Correct output int: "<<m_intImportantNumber<<endl;
		cout<<"Correct output double: "<<m_dblOtherNumber<<endl<<endl;
 
		m_dblOtherNumber = 6.28; //Yes you may change this due to "mutable" keyword.
	//The line below will cause a compile time error because it is not mutable.
        //----- m_intImportantNumber = 200 -----// Outcommented. It is only for demonstrational purposes here.
 
		cout<<"Message generated by Print() const AFTER changing double:"<<endl;
		//Look at the consequences.:
		cout<<"Old int is still the same: "<<m_intImportantNumber<<endl;
		cout<<"New double member value (changed on purpose): "<<m_dblOtherNumber<<endl<<endl;
	}
 
	void SetNums(int i, double f)
	{
		m_intImportantNumber = i;
		m_dblOtherNumber = f;
	}
};
 
int main(int argc, char* argv[])
{
	double pi = 3.14;
	SomeClass obj;
	obj.SetNums(12, pi);
	obj.Print();
 
	return 0;
}

- The keyword "mutable" can only be used on class members.
- Mutable class member objects can only be changed by class member methods. Not by "free" methods.

Just a few interesting snippets:
1) Compile time constants in classes.
2)Function overloading.

class CMyArray
{
private:
	const int SIZE;
	int MySpecialArray[]; //Illegal. const array size needs an initialized size at compile time remember?
}

- Solution (actually not a solution, it is a HACK):
#include <iostream.h>
 
class SomeClass
{
private:
 
	 enum{ SIZE = 100}; //Avoiding the need for a compile time value for SIZE. Hack!.
	 int MySpecialArray[]; //OK.
};

- These two functions are not the same.
void SomeClass::Print() const;
void SomeClassPrint();

What will happen in the code below?
Run to see.
#include <iostream.h>
 
class SomeClass
{
private:
 
public:
	void Print()  const { cout <<"Print() const { ... }"<<endl; }
	void Print() { cout<< "Print() { ... }"<<endl; }
};
 
int main(int argc, char* argv[])
{
	SomeClass obj;
	obj.Print();
	return 0;
}

Question:
What is needed to make "void Print() const " usable too? Without removing the other Print() method obviously!

hint:
SomeClass obj; <== const?

Brain mixed up?
- Take your time.
- Use always const when objects need to (and should) be protected.
- Don't use an in-between style. THAT will cause problems.

End of tutorial

Mail to for comments. Comments that might lead to a better tutoring style is very welcome.

Post new comment

  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
  • You can use BBCode tags in the text.
  • You can enable syntax highlighting of source code with the following tags: [code], [blockcode], [asp], [awk], [c], [cpp], [csharp], [drupal5], [drupal6], [java], [javascript], [jquery], [mysql], [perl], [perl6], [php], [python], [ruby], [sql], [vb], [vbnet], [xml].
  • Use to create page breaks.

More information about formatting options

By submitting this form, you accept the Mollom privacy policy.