
MORE ON CONSTRUCTORS
---------------------

Constructors are automatically invoked by the compiler when a class object is
declared.  As such, the cannot be invoked in the "regular" manner as a method
function is invoked.  The following program will generate a compile-time error:

#include <iostream>
#include <string>

using namespace std;

class C
{
 int x ;
public:
 C();
 void print(void);
} ;

// define the default constructor

C :: C()
{
 cout << "default constructor called" << endl;
 x = 1;
};

 void C :: print(void)
{
 cout << x << endl;
}




int main()
{

C x;
x.print();

// see if we can invoke a constructor explicitly...

x.C();


// nope it gives a compile time error
//constructor.C: In function `int main()':
//constructor.C:44: error: calling type `C' like a method



return 0;
}


In this program, we attempt to invoke explicitly the constructor C() using
the "object_name.method_name() style.  This results in a compile time error.

There is a way to explicitly invoke the constructor function, but is has to be
called at the time of the object's declaration as in:

#include <iostream>
#include <string>

using namespace std;

class C
{
 int x ;
public:
 C();
 C(int num);
 void print(void);
} ;

// define the default constructor

C :: C()
{
 cout << "default constructor called" << endl;
 x = 1;
};


C :: C(int num)
{
 cout << "overloaded constructor called" << endl;
 x = num;
};

 void C :: print(void)
{
 cout << x << endl;
};




int main()
{

C x = C(55);   // make a call to the overloaded constructor at time of object declaration
               // to perform initialization
x.print();


C y = C();  // make a call to the default constructor at time of object declaration
            // to perform initialization of data member

y.print();


return 0;
}


The program output is:

overloaded constructor called
55
default constructor called
1



Member Initialization in Constructors:
--------------------------------------

C++ allows an alternate form for a constructor to perform member initialization
by a mechanism known a "base/member initialization". The following program
illustrates its use:

#include <iostream>
#include <string>

using namespace std;


class X
{
private:

 int i;
 float f;
 char c;

public:

X(int first=1, float second=2.0, char third='a') : i(first) , f(second) , c(third) { } 
void print() { cout << i << " " << f << " " << c << endl;}

};



int main()
{

X var1;
var1.print();

return 0;
}


We may override the default arguments provided by the definition of the constructor,
by providing them when we declare an object:

int main()
{

X var1, var2(6,2.34,'z');
var1.print();
var2.print();

return 0;
}

will produce the output:

1 2 a
6 2.34 z


What if we had a third object declared as:

X var3(7) ;

the value of 7 would be used to initialize the data member i, and
the other two date members would use the provided default values in the
constructor ( 2.0 and 'a' )






ASSIGNMENT of one class object to another of the same type
-----------------------------------------------------------



An object of a class may be assigned to another object of the same class.


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


class X
{
private:

 int i;
 float f;
 char c;

public:

X() { i = 1 ; f = 2.0 ; c = 'a' ; cout << "default called" << endl;}
X(int first, float second, char third) { i = first; f = second; c = third;
                                         cout << "overloaded called" << endl;
                                       }
void print() { cout << i << " " << f << " " << c << endl;}

};



int main()
{

X var1, var2(10,100.0,'z');
var1.print();
var2.print();

var2 = var1 ;  // the compiler provides a default assignment operator which simply
               // performs a member by member assignment

var2.print();


return 0;

}


The output is:

default called
overloaded called
1 2 a
10 100 z
1 2 a


We can always provide our own version of the overloaded assignment (=) operator
instead of relying upon the one which is provided default by the compiler:



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

class X
{
private:

 int i;
 float f;
 char c;

public:

X() { i = 1 ; f = 2.0 ; c = 'a' ; cout << "default called" << endl;}
X(int first, float second, char third) { i = first; f = second; c = third;
                                         cout << "overloaded called" << endl;
                                       }
void print() { cout << i << " " << f << " " << c << endl;}
void operator=(X some_object);
};


void X::operator=(X some_object)
{
 cout << "using the user-defined assignment operator instead of compiler provided" 
      << endl;

// now simply copy the values of the data members of some_object 

i = some_object.i;
f = some_object.f;
c = some_object.c;

};


int main()
{

X var1, var2(10,100.0,'z');
var1.print();
var2.print();

var2 = var1 ;  // use the programmer supplied version of the assignment operator
               // this is the same as var2.operator=(v1);



var2.print(); // print out the values of the data members after the assignment

return 0;

}

The output is:

default called
overloaded called
1 2 a
10 100 z
using the user-defined assignment operator instead of compiler provided
1 2 a


Why would we want to provide our own version of the assignment operator
when the compiler always provides its own default one??? There are certain
cases in which a class contains a pointer variable in which the compiler provided
assignment operator is "not good enough". In order to understand why we have to 
learn a little about garbage and dangling pointers.....


Garbage and Dangling Pointers
------------------------------


The following program produces what is known as a "dangling pointer":



#include <iostream>
#include <string>

using namespace std;



int main()
{

int* ptr ; // declare a pointer to an int

ptr = new int (5); // request space from the heap , initialize this space to 5
                  // and assign address of allocated space to ptr


cout << "The value of ptr is " << ptr << " and the data stored in this location is " 
     << *ptr << endl;


delete ptr ; // give back the allocated space  to the heap 


// ptr is now called a dangling pointer.. it does not point to any
// valid memory location... it should not be dereferenced until it
// has been assigned some valid address... note the value of
// ptr is not affected by the delete operator... it still
// holds the same address... except that this address is "no longer
// in existence"

cout << "The value of ptr after the delete is " << ptr << endl;



return 0;
}


Garbage is the result of when we dynamically allocate memory from the heap,
assign it's address to some pointer variable, and then assign some other
address to the pointer variable BEFORE we have deleted the allocated memory
(returned it back to the heap).  The allocated memory can now NEVER be returned
back to the heap (at least until the program completes execution) since we now
longer have its address available. Such memory is referred to as GARBAGE.
The following program illustrates how a program may generate garbage.

#include <iostream>
#include <string>

using namespace std;



int main()
{

int* a = new int(10);

cout << "an integer with value 10  has been allocated from the heap at address " << a << endl;


a = new int(2) ;   // the memory which was allocated to hold the integer
                   // with initial value is lost and cannot be reclaimed
                   // since we no longer have its address ... It is
                   // said to be GARBAGE.


cout << " the original pointer holding the integer value with 10 is now assigned a new address of" << a << endl;

delete a  ; // give back the second integer to the heap


return 0;
}


The output is:

an integer with value 10  has been allocated from the heap at address 0x21200
 the original pointer holding the integer value with 10 is now assigned a new address of0x21210



If a class contains data members which have been made to point to dynamically
allocated memory , and a default assignment operator is used to assign one object
to another, garbage can result as in the following example:



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


class X
{
private:

int* ptr;

public:

X() { ptr = new int ;}
void show_address() { cout << "Value of ptr is: " << ptr << endl;} 
};



int main()
{

X var1, var2;
var1.show_address();
var2.show_address();

var2 = var1 ;  // the compiler provides a default assignment operator which simply
               // performs a member by member assignment


var1.show_address();
var2.show_address();


return 0;

}


The output is:

Value of ptr is: 0x211e8
Value of ptr is: 0x211f8
Value of ptr is: 0x211e8
Value of ptr is: 0x211e8


After the statement var2 = var1 ; we no longer have a pointer to the
memory which was allocated from  0x211f8 because var2.ptr has been overwritten
with value of var1.ptr (which points to some other place in the heap). We have
created some garbage at address  0x211f8 and this garbage cannot be de-allocated
with the delete operator since we no longer have its address.


Even if the class contains a destructor function, the use of the compiler
provided assignment operator will result in a dangling pointer and garbage.
The details of the "picture" of main memory in the case  of a user-provided
destructor functions which simply performs ~X() { delete ptr } is left to
the interested reader... I highly encourage you to go through this constructive
exercise.






We can provide our own version of the assignment operator which circumvents
this problematic behaviour:


// Author: Ted Obuchowicz
//  nov. 14, 2008




// Author: Ted Obuchowicz
//  nov. 14, 2008



#include <iostream>
#include <string>

using namespace std;


class X
{
private:

int* ptr;

public:

X() { ptr = new int(5) ;} // allocated memory from the heap and intialize it to 5
void show_address() { cout << "Value of ptr is: " << ptr << 
                      " Value stored in this location is " << *ptr << endl;} 
void operator=(X arg);
};


void X::operator=(X arg)
{
 if ( ptr != 0)
 {
   cout << "About to delete the space at address " << ptr << endl;
   delete ptr;
 };
 ptr = new int; // allocate space from the heap
 *ptr = *(arg.ptr); // fill this newly allocated memory with the same value as the argument
}; 


int main()
{

X var1, var2;
var1.show_address();
var2.show_address();


var2 = var1 ; // no more garbage

var1.show_address();
var2.show_address();


return 0;

}


//output:


//Value of ptr is: 0x213e8 Value stored in this location is 5
//Value of ptr is: 0x213f8 Value stored in this location is 5
//About to delete the space at address 0x213f8
//Value of ptr is: 0x213e8 Value stored in this location is 5
//Value of ptr is: 0x213f8 Value stored in this location is 5


