Advanced C++ - Part V


The following topics are covered in this section:


Casting

Casting basically means forcing something into something different. For instance if you have an integer division, you can change (or rather force) it into a floating type division through casting. The general form of cast is:

        (new type) expression;

Casting might also be referred to as typecasting (because we are casting between data types). The following example should make the concept clear:

int main( )
{
int i=5;
cout<<i/2;
return 0;
}

The output would be 2 because ‘i' is an integer and the compiler will perform integer division.

Using casting, the above program will be modified as below:

int main( )
{
int i=5;
cout<<(float)i/2;
return 0;
}

The output now will be 2.5

This method of casting was actually part of C programming. In C++ we have another four methods of casting. In this section we’ll discuss them briefly. These operators were introduced in C++ to restrict casting and enforce some rules in the process of casting. Otherwise using the C type of casting a programmer can indiscriminately cast and it could lead to unexpected results or errors.

The general syntax for using these casting operators is as below:

        operator_cast <new-type> (expression/variable)

where operator_cast is replaced by one of the 4 casting operators.

dynamic_cast:

This is used to cast a base class pointer/reference to a derived class pointer/reference and vice-versa. It is dynamic because it will determine at runtime whether a base class pointer refers to an object of the base class or whether it points to an object of a derived class (this is known as downcasting-going from base to derived).

The opposite is known as upcasting. In upcasting, we can cast a pointer of a derived class to a pointer of base class (we go from derived to base).

This will work only on polymorphic classes (polymorphism means that at least one of your base class functions should be a virtual function). This operator will check at runtime to make sure that the casting was properly carried out. Suppose you are casting a pointer and if the casting is unsuccessful (i.e. it wasn’t cast to the requested type), then the value returned will be NULL. If you are using references and if the casting fails, then a bad_cast exception will be thrown.

This must be confusing you so let’s take up an example.

class car
{ public:
virtual void disp( )
{ }
};
class ferrari : public car
{ };
int main( )
{ car *cp1, *cp2;
ferrari *fp;
cp1 = new ferrari;
cp2 = new car;
fp = dynamic_cast<ferrari*>(cp1); // successful
if (fp)
{
cout<<"successful cast-base points to derived object";
}
else
{
cout<<"casting failed";
}
fp = dynamic_cast<ferrari*>(cp2); // will fail fp value is NULL
if (fp)
{
cout<<"successful casting";
}
else
{
cout<<"casting failed-base points to base object";
}
return 0;
}

The output is:

successful cast-base points to derived object
casting failed-base points to base object

Take note of the following points (regarding cp1, cp2 and dynamic_cast):

1.) The casting will be successful if the base class pointer points to a derived object type. ‘cp1’ points to an object of type ‘ferrari’. Hence,

        fp = dynamic_cast<ferrari*>(cp1);

will be successful.

2.) The casting will fail if the base class pointer points to a base class object. ‘cp2’ points to an object of type ‘car’ (which is the base class). Hence,

        fp = dynamic_cast<ferrari*>(cp2);

will fail.

3.) You base class should be polymorphic (it should contain a virtual function). Otherwise you’ll get an error while using dynamic_cast.

static_cast:

This is similar to the C type of casting discussed earlier. The syntax is:

        static_cast<new-type> (expression/variable)

A simple program is written below:

int main( )
{
int i=5;
cout<<static_cast<float>(i)/2;
return 0;
}

Basically we are casting ‘i' into a floating type. The expression i/2 will be equal to dividing a floating value by 2.

Beware: What would happen if we write it as:

cout<<static_cast<float>(i/2);

Do you think there will be any change in output? Well, in fact the output will now be 2 and not 2.5. The reason is because the compiler would already have divided i/2, the result would be 2 (because of integer division) and the value of 2 will be forced into floating type (which makes no difference because you have already lost the decimal place). So be very careful while casting.

Static_cast performs no run-time check. This cast operator is not used only for standard data types; it can be used for casting pointers to classes.

reinterpret_cast:

The reinterpret_cast can be used to convert between two totally different types. It is used in situations where none of the other operators can be used. It can cast pointers of one type into another type or it can also convert from pointer to integer and vice-versa. Since you can cast pointer from one type into another type, using reinterpret_cast we can cast from between two pointers pointing to totally unrelated classes.

class bacteria
{ public:
void disp( )
{
cout<<"\nThis is bacteria";
}
};
class car
{ };
int main( )
{ bacteria *bp;
car *cp;
car mine;
cp=&mine;
reinterpret_cast<bacteria*>(cp)->disp( );
return 0;
}

The output is:

This is bacteria

Just go back to this line in the program:

reinterpret_cast<bacteria*>(cp)->disp( );

‘cp’ is actually a pointer to the class ‘car’. Here we have forced this pointer to point to the class bacteria and using this pointer we call the bacteria member function disp ( ). The output illustrates that the casting was successful.

const_cast:

None of the other 3 casting operators that we’ve seen can get rid of the constant-ness of an object. For example: we discussed about constant objects and these objects can access only constant functions. Constant functions cannot modify member data (unless the data is mutable). Using the const_cast operator we can get rid of the constant-ness of the object; thus permitting it to modify member values. Constant objects are usually not supposed to change member data values but sometimes there are member data which are hidden from the user (but necessessary for all objects-irrespective of whether the object is a constant object or not). In such cases, there has to be a mechanism to modify the data (even by constant objects). One way is to use the ‘mutable’ method and the other method is to use the const_cast operator.

#include <iostream>
using namespace std;
class bacteria
{ private:
int life;
public:
bacteria( )
{
life=0;
}
void set( ) const
{
cout<<"The original bacteria life is : "<<life;
const_cast<bacteria*>(this)->life=1;
cout<<endl<<"The new life value is : "<<life;
}
};

int main( )
{ bacteria const ecoli;
ecoli.set( );
return 0;
}

The output is:

The original bacteria life is : 0
The new life value is : 1

In the above case we have cast away the constant-ness of the ‘this’ pointer and hence it can modify the member data ‘life’.


An introduction to STL (Standard Template Library)

This section will give you a brief introduction into STL. We learnt about templates earlier and the STL provides us with general-purpose template classes and functions to implement some common data structures and algorithms. The major types are stacks, queues, lists and vectors. As you know, templates can be used for any type of data and the STL is a generic library. It not only defines the general structure but also provides with some useful functions that can be used to operate on them.

The three main aspects of STL are:

Container Classes:

These are classes that will contain/hold other objects. The various containers available are stacks, deque (double ended queue), lists, vectors, maps etc. I’ll just give you an idea as to what these containers generally mean.

Stacks: This is a way to store data such that whatever data you put in first can be taken out last. In other words, just imagine that you are piling up books one on top of the other. This forms a stack of books. The book that you kept at the bottom was the first book that you kept. The book that was kept at last will be right on top of the stack When you want to retrieve the books, the topmost book will be the first to be retrieved (hence what went in last will come out first). This is known as the last-in-first-out (LIFO) principle.

Queues: This is similar to our real life queues. The first person who enters will be the first person to leave the queue. The principle here is first-in-first-out (FIFO).

Now the container classes will contain functions to operate on them. In the case of the stack you will usually come across the terms ‘push’ and ‘pop’. If you add a new item to the stack, you are said to ‘push’ into the stack. When you retrieve a data from the stack you are said to ‘pop’ from the stack. Thus these operations are modeled into functions.

Algorithms:

Algorithm basically means how you are going to operate on a container class. For instance, there are algorithms for sorting values in a container (sorting in ascending order), there are algorithms to reverse the order of elements present in a container and so on. Algorithms will operate on the containers.

Iterators:

Iterators are similar to pointers. They can be used to access particular elements of a container. The relation between iterator and container is similar to the relation between pointer and array. Either you can access a particular element of the container or you can go through all the elements. There are different types of basic iterators:

Forward iterator – this will move in a forward direction from one element to the next. It can be used for storing as well as retrieving values.

Random Access – This is used to access elements randomly.

Bidirectional – This is similar to the forward iterator except that this can move in both directions.

Let’s take a look at a simple program to illustrate vectors (and STL in general).

Vectors: Vector container is similar to the C++ array type. It can hold objects of the same data type. Vector container permits random accessing and it is dynamic (i.e. it can keep increasing in size as and when needed). The vector will allocate the required memory space whenever the vector grows in size.

#include <iostream>
#include <vector>
using namespace std;

int main( )
{
vector<int> vec; //creating an object of vector
vec.push_back(1); //push_back will put a value into the vector
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);

cout<<"Size of the vector : "<<vec.size( );
cout<<endl<<"The elements are: "<<vec[0]<<" "<<vec[1]<<" "<<vec[2]<<" "<<vec[3];

vec.erase(vec.begin( )+1,vec.end()-1);
cout<<endl<<"Size of the vector after erasing: "<<vec.size( );
cout<<endl<<"The elements are : "<<vec[0]<<" "<<vec[1];

vec.clear( );
cout<<endl<<"Size of the vector after clearing: "<<vec.size( );

return 0;
}

The output is:

Size of the vector : 4
The elements are: 1 2 3 4
Size of the vector after erasing: 2
The elements are : 1 4
Size of the vector after clearing: 0

The various functions that have been used in the program will be explained (the various function syntaxes will not be dealt with since this program is to give you a feel of STL).

vec.size( );

This will return the size of the vector (how many elements are contained in the vector).

vec.clear( );

The clear ( ) function will delete all the value present in the vector object. Thus the size after clearing is 0. You would have noticed that we can access the vector elements by using index numbers (just like we do for arrays):

vec[0];

Another function that has been used is the erase function:

vec.erase(vec.begin( )+1,vec.end( )-1);

This function is used to selectively delete particular values from your vector object. Deleting doesn’t simply delete the values, but it also reorders the vector. In the above example the vector initially had 4 values:

1 2 3 4

Using the erase function we asked the compiler to delete the values from the second element to the second last element (which means that 2 and 3 have to be deleted). The compiler will delete 2 and 3 and then it will put the value 4 in the second position of the vector. Hence the vector now contains:

1 4

and the size of the vector is now 2.

Some of the functions like size ( ) and push_back ( ) are available to the other container classes as well. This section was intended to just give you an idea about STL.


Go back to the Contents Page 2


Copyright © 2004 Sethu Subramanian All rights reserved.