|












| |
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.
|