C++ Tutorial

Home
SOA
Spring-Hibernate
GOF Design Patterns
Java BP
Java Tutorial
Java Traps
Datawarehouse
C++ Tutorial
Unix Commands
Interview QnA
About MDC

All about C++ [Author: Govind K Mekala]

CHAPTER INDEX :

@1: A, B, C of Objects
@2. Dynamic Object Creation
@3 Containers and Inheritence
@4 Access control and friend
@5 Virtual, Static functions and Polymorphism, Pure Virtual Functions,Abstract Classes
@6 Multiple Inheritence or Virtual inheritence or Diamond Inheritence
@7 Templates
@8 Exception handling and error detection
@9 RTTI (RunTime Type Identification)
@10 Design patterns
 

@1: A, B, C of Objects: <CHAPTER INDEX>

The object-oriented approach provides tools for the programmer to represent elements in the problem space. This representation is general enough that the programmer is not constrained to any particular type of problem. We refer to the elements in the problem space and their representations in the solution space as "objects".(From "Thinking in C++" by BruceEckel)

Although very well portrayed this definition doesn't help in understanding what hell is this stuff called object.

Object is an advanced version of C structures to package data and methods together to achieve
A. Data abstraction. B. Implementation Hiding (Encapsulation) C. Code reusability

Lots of things will get clear in the following example. Read carefully the C++ comments after // or in between /* */.

Project Manager Example:

@2. Dynamic Object Creation:  <CHAPTER INDEX>

When a C++ object is created, two events occur:
1. Storage is allocated for the object.
2. The constructor is called to initialize that storage.
By now you should believe that step two always happens. C++ enforces it because uninitialized objects are a major source of program bugs. It doesn't matter where or how the object is created ,the constructor is always called.
Step one, however, can occur in several ways, or at alternate times:
1. Storage can be allocated before the program begins, in the static storage area. This storage exists for the life of the program.
2. Storage can be created on the stack whenever a particular execution point is reached (an opening brace). That storage is released automatically at the complementary execution point (the closing brace). These stack-allocation operations are built into the instruction set of the processor and are very efficient. However, you have to know exactly how much storage you need when you're writing the program so the compiler can generate the right code.
3. Storage can be allocated from a pool of memory called the heap (also known as the free store). This is called dynamic memory allocation. To allocate this memory, a function is called at runtime; this means you can decide at any time that you want some memory and how much you need. You are also responsible for determining when to release the memory, which means the lifetime of that memory can be as long as you choose - it isn't determined by scope.
Often these three regions are placed in a single contiguous piece of physical memory: the static area, the stack, and the heap (in an order determined by the compiler writer). However, there are no rules. The stack may be in a special place, and the heap may be implemented by making calls for chunks of memory from the operating system. As a programmer, these things are normally shielded from you, so all you need to think about is that the memory is there when you call for it.
To allocate memory dynamically at runtime, C provides functions in its standard library: malloc( ) and its variants calloc( ) and realloc( ) to produce memory from the heap, and free( ) to release the memory back to the heap. 

ex: Obj* obj = (Obj*)malloc(sizeof(Obj));
free(obj);
Here, the user must determine the size of the object (one place for an error). malloc( ) returns a void* because it's just a patch of memory, not an object. C++ doesn't allow a void* to be assigned to any other pointer, so it must be cast.

C++ provides new and delete for dynamic object cration and deletion.

ex: MyType *fp = new MyType(1,2);
delete fp;

No equivalent of realloc and calloc in C++.

New and delete are better than malloc and free because new returns pointer of the correct type whereas malloc returns void* which is not type safe. New operator can be overloaded.

@3 Containers and Inheritence: <CHAPTER INDEX>

Creating an instance of one object in another class is nothing but containership or composition. You need to know the instance vaiable name to access it. This process is called delegation
ex:
// embedded objects
#include "X.h"
class Y {
int i;
X x; // Embedded object
public:
Y() { i = 0; }
void f(int ii) { i = ii; x.set(ii); }
int g() const { return i * x.read(); }
void populate() { x.populate(); }
};

int main() {
Y y;
y.f(47);
y.populate();


Class Y is "implemented-in-terms" of class X. Y class "has a" embedded object of typeX.

When you inherit, you are saying, "This new class is like that old class." You state this in code by giving the name of the class, as usual, but before the opening brace of the class body, you put a colon and the name of the base class (or classes, for multiple inheritance). When you do this, you automatically get all the data members and member functions in the base class. 

example: 
// Simple inheritance
#include "X.h"
#include
using namespace std;

class Y : public X {
int i; // Different from X's i
public:
Y() { i = 0; }
int change() {
i = populate(); // Different name call
return i;
}
void set(int ii) {
i = ii;
X::set(ii); // Same-name function call
}
};

int main() {
cout << "sizeof(X) = " << sizeof(X) << endl;
cout << "sizeof(Y) = "
<< sizeof(Y) << endl;
Y D;
D.change();
// X function interface comes through:
D.read();
D.populate();
// Redefined functions hide base versions:
D.set(12);
} ///:~

To guarantee proper initialization during composition and inheritance we use constructor initializer list.
ex: MyType::MyType(int i) : Bar(i),memb(i+1) { // ...

We seperate the constructor by comma in case of contanership and by colon : in case of inheritence.
Order of constructor & destructor calls:

first base class constructor is called.
then child or derived constructor is called
then child destructor is called
then base destructor is called.

Constructors and destructors don't inherit. Also the operator= doesn't inherit because it performs a constructor-like activity

Acess modification:

Public Inhertence (class Y:public X)

private members doesn't get inherited.
protected members of base class become protected in derived class.
public member of base class become public in derived class.

Private Inheritence (class Y:private X)
Private members doesn't get inherited.
Protected members of the base class become private in derived class.
Public members also become private in derived class.

Protected Inheritence: (class Y: protected X)
Private members doesn't get inherited.
Protected members become private in derived class.
Public members become protected.

@4 Access control and friend : <CHAPTER INDEX>


A PRIVATE member of a class is accesible(using dot operator) only by members and friends of the class. Its the default access.
A PROTECTED member of a class is accessible by members and friends of publically derived(inherited) class provided they access the base member via a pointer or reference to their own derived class.
A PUBLIC member of a class is accessible by everyone.

A friend function acts as a bridge between two classes and they can access all the mebers of the class in which they are declared.
#include

class beta;//forward declaration

class alpha
{
private:
int data;
public:
alpha(){data=5;}
friend int fri(alpha,beta); //friend function
};
class beta
{
private:
int data;
public:
beta(){data=9;}
friend int fri(alpha, beta);//friend function
};

int fri(alpha a, beta b)
{
return (a.data + b.data);
}
void main()
{
alpha aa;
beta bb;
cout < }

FRIEND CLASSES
When a class is declared as friend of another class all the member fuction of that class becomes friend function of the other class and have access to all members of that class.

#include
class beta // forward declaration
class alpha
{
private:
int data1;

public:
alpha() {data1=55;}
friend beta; /*beta is a friend class. Beta member function can access alpha
if we don't declare beta in forward declaration then the syntax for friend class will be friend class beta */
};

class beta {
public:
void func1(alpha a) { cout<< "\n data1="< };
void main()
{
alpha a;
beta b;
b.func1(a);
}
Friendship is neither commutative nor inherited nor associative. If there is two overloaded function then you have to declare both of them friend in order to achieve friendship otherwise the other function will become hidden and hence can't be accessed.

@5 Virtual, Static functions and Polymorphism, Pure Virtual Functions,Abstract Classes: <CHAPTER INDEX>

polymorphism is the abilty to treat many objects of different but related types without regard to their differences. An entity could behave diferently at the run time depending on its association with different classes in the inheritence tree. In C++ its achieved by using class derivatives and inheritence and virtual functions.
Virtual functions enables dynamic binding.

Class A{
nvfunc() {.......}
virtual vfunc(){.....}
}
class B: public A
{
nvfunc() {..........}
vfunc(){..........}
}
class C:public A
{
vfunc(){.........}
}

void main()
{
A *ap;
A a;
B b;
C c;
b.A::nvfunc(); will call method in A;
ap = &b;// Downcast base class pointer to derived class.
ap.nvfunc();//Will call method in A. The fact that ap actually points to b is not taken //into account as its not declared as virtual
ap.vfunc();//Will call method in B as the method is declared as virtual
//using refernce operator
A &a1 = c;
a1.vfunc();//will call method in C using dynamic dynamic binding.
}

Pure virtual function is the member function that the base class forces derived classes to provide and its equated to 0.
ex: virtual vfunc()=0;
Abstract classes can not be instantiated directly and only can be be inherited.
It should not contain any variable and should contain atleast one pure virtual function equated to zero.

When a virtual function is created in an object, the object must keep track of that function.
Many compilers build a virtual function table called V-Table. One of these is kept for each type and each object of that type keeps a virtual table pointer (4 byte header) called vptr or v-pointer which points to that table. Each object's vptr points to the v-table which in turn has a pointer to each of the virtual functions. This is an overhead and takes CPU cycles to resolve the correct function.
Whenever the derived class constructor is called and its object is added, the vptr is adjusted to point to the virtual function override (if any) in the derived object.

Base class pointer is always type compatible with pointer to all derived class objects.

No Constructors can be virtual as derived class constructor should not be called without calling base class constructor. 
Virtual destructor is possible. When pointer to the derived object is passed, where pointer to the base object is expected and that pointer is deleted, if the destructor is virtual the derived class' destructor is called. As the derived class destructor automatically invoke the base class' destructor, the entire object will be properly destroyed.
Virtual destructor should be always defined whenever there is any other virtual function in the base class.

Static functions: Static member data are duplicated for every object created. Instead they are class variables like const members and same memory is shared among objects. static member functiones are defined to access static data members. 
#include
class MyClass
{
private:
static int count;
int index;
public:
gamma()
{
count++;
index = count;
}
~gamma()
{
count--;
cout << "\n Destroying index=" << index;
}
static void getcount()
{
cout << "\nTotal count is " << count;
}
void getindex()
{
cout<< "\nIndex is " << index;
}
};

void main()
{
cout << endl << endl;
MyClass m1;
MyClass::getcount();//static functions are accessed using scope operator ::

MyClass m2,m3;
MyClass::getcount();

m1.getindex();
m2.getindex();
m3.getindex();
}

@6 Multiple Inheritence or Virtual inheritence or Diamond Inheritence: <CHAPTER INDEX>

Is used in c++ when you do not want two copies of shared base class but rather to have a single shared base class. Make the base class as virtual base class using virtual inheritence. Normally a class' constructor initializes only its own variables and its base class. Virtually inherited base classes are an exception, however. They are initialized by their most derived class.

class virtualbaseclass
{
protected basedata;
};
class derived1:virtual public virtualbaseclass;
{};
class derived2: virtual public virtualbaseclass;
{};
class mostderived: public virtualbaseclass, public virtualbaseclass
{
public:
int getdata()
{ return basedata;} //only one copy of base class due to virtual base class
}
The use of keyword virtual in these two classes cause them to share a single common subobject of their base class parent. Hence there in no ambiguity in most derived class for basedata.

@7 Templates: <CHAPTER INDEX>

Templates allow the separation of the type of data they manipulate. A template allows the separation of the type-dependent part from type-independent part. This facilitates code sharing.

syntax :
template
{
}

T represents unspecified type in the classtemplate defintion.
Template class is instantiated by specifying the type T.
Libraries like STL, ATL consists of various generic templates.
A complete example:

#include
#include
#include
template
class TemplateArray{
public:
TemplateArray(unsigned size=12);//default constructore
TemplateArray(const Array& a); //copy constructor assignment
~TemplateArray() throw();
unsigned getsize() ;
T& operator[](unsigned i);

protected:
unsigned size;
T* arr;
}

template inline TemplateArray::TemplateArray(unsigned sz):size(sz),arr(new T[sz])
{}
template inline TemplateArray::~TemplateArray()
{ delete[] arr;}
template inline T& TemplateArray::operator[](unsigned i)

assert(i >= size);
return arr[i];
}

int main()
{
TemplateArray ai; ai[2]=5;
TemplateArray ac; ac[3]='G';
TemplateArray as; as[5]="Govind";
}

The complier generates the code for the instatiated template by replacing the template arguement T with the type that is supplied, such as int or string.

Function template example:

#include
template
void swap(T& a, T& b)
{
T temp = a;
a = b;
b = temp;
}

int main()
{
int x =10,y=20; swap (x,y);
cout<<"x="<<x<<","<<"y="<<y;<br /> swap(s,d);
cout<<"s="<<s<<"}

@8 Exception handling and error detection: <CHAPTER INDEX>

Exception handling is done using try & catch blocks.

try{
............
}
catch(ExceptionClass e)
{
// handle exceptions 
...........
throw();
}
catch(AnotherException e1)
{
// handle exceptions 
.........
throw();
}

example:

// Basic exceptions
// Exception specifications & unexpected()
#include
#include
#include
#include
using namespace std;

class Up {};
class Fit {};
void g();

void f(int i) throw (Up, Fit) {
switch(i) {
case 1: throw Up();
case 2: throw Fit();
}
g();
}

// void g() {} // Version 1
void g() { throw 47; } // Version 2
// (Can throw built-in types)

void my_unexpected() {
cout << "unexpected exception thrown";
exit(1);
}

int main() {
set_unexpected(my_unexpected);
// (ignores return value)
for(int i = 1; i <=3; i++)
try {
f(i);
} catch(Up) {
cout << "Up caught" << endl;
} catch(Fit) {
cout << "Fit caught" << endl;
}

 

The classes Up and Fit are created solely to throw as exceptions. 
f( ) is a function that promises in its exception specification to throw only exceptions of type Up and Fit, and from looking at the function definition this seems plausible. Version one of g( ), called by f( ), doesn't throw any exceptions so this is true. But then someone changes g( ) so it throws exceptions and the new g( ) is linked in with f( ). Now f( ) begins to throw a new exception, unbeknown to the creator of f( ). Thus the exception specification is violated.
The my_unexpected( ) function has no arguments or return value, following the proper form for a custom unexpected( ) function. It simply prints a message so you can see it has been called, then exits the program. Your new unexpected( ) function must not return (that is, you can write the code that way but it's an error). However, it can throw another exception (you can even rethrow the same exception), or call exit( ) or abort( ). If unexpected( ) throws an exception, the search for the handler starts at the function call that threw the unexpected exception. (This behavior is unique to unexpected( ).)
Although the new_handler( ) function pointer can be null and the system will do something sensible, the unexpected( ) function pointer should never be null. The default value is terminate( ) (mentioned later), but whenever you use exceptions and specifications you should write your own unexpected( ) to log the error and either rethrow it, throw something new, or terminate the program.
In main( ), the try block is within a for loop so all the possibilities are exercised. Note that this is a way to achieve something like resumption - nest the try block inside a for, while, do, or if and cause any exceptions to attempt to repair the problem; then attempt the try block again.
Only the Up and Fit exceptions are caught because those are the only ones the programmer of f( ) said would be thrown. Version two of g( ) causes my_unexpected( ) to be called because f( ) then throws an int. (You can throw any type, including a built-in type.)
In the call to set_unexpected( ), the return value is ignored, but it can also be saved in a pointer to function and restored later.

@9 RTTI (RunTime Type Identification): <CHAPTER INDEX>



Static type checking is when compiler checks the type correctness of operation at compile time.
If class M has member function f() but not e() and X( an instance of M) tries to access X.e().
This type of error will be caught at compile time itself.

RTTI is one type of dynamic type checking that is supported by C++. Its accomplished by downcasts.

Downcasts is the conversion of a base class pointer to publically derived class pointer.
The RTTI casts are:
dynamic_cast()
static_cast()
typeid()
const_cast()
reinterpret_cast()

if p is a pointer, dynamic_cast(p) converts p to MyClass. If the conversion is not valid it returns null.
similarly using refernce
if r is a refernce, dynamic_cast(r) converts r to a MyClass&. If the conversion is not valid, an exception of type bad_cast is thrown.

example:

#include
using namespace std;

class A {
public:
virtual ~A() throw() { };
//................
}

A::~A() throw(){ }

class B:public A {/*.........*/};
class C: public A {/*.........*/};

void check(A* a) throw()
{
A* a1 = dynamic_cast(a);
if(a1!=NULL) {
cout<< "The object is of type A\n";
}
else{
cout<< "The object is not of type A\n";
}
}

int main()
{
B bb;
C cc;
check(&bb);
check(&cc);
}

static_cast() is used to enforce the cast when the programmer is confident of the type and don't want the compiler to bug him at compile time.

Target *t = static_cast(sourceobject); // cast without checking

typeid() determines the correct type of an object at runtime. It returns an object of type_info class when a pointer or reference is passed as an arguement. It returns NULL if the type could not be determined. The member called name in type_info returns the name of that class.
example:

int iobj;
cout << typeid(iobj).name()< cout <
class Govind {/* no virtual function*/};
class Krishna: public Govind{/* no virtual function*/};

Krishna kris;
Govind gd;
Govind *go = &kris;
Govind *g = &gd;
cout << typeid(*go).name()< if(typeid(g)==typeid(go))//false

@10 Design patterns: <CHAPTER INDEX>
Read advanced C++ Features, Design Patterns, and Frameworks

This tutorial presents three complete case studies that illustrate how patterns from the ``Gang of Four'' can be combined with advanced C++ features to build flexible, reusable, and efficient frameworks and components. The case studies include building a system sort framework, a sort verification framework,and a class library for expression trees.