2-1) Class Data and Initialization

revisions:

Oct 6, 2008 reformatted example code at the end of this section

Oct 1, 2007 Changed the sample code to compile with Visual C++ 2005 Express Edition and also use the strcpy_s rather than strcpy from the string.h library


Sample Code

Classes represent incapsulated memory

The memory of an object are the variables described in its class and those inherited from superclasses (base classes)

The memory is initialized when the memory is allocated and constructors are run

Super class constructors need to run to initialize memory specified in the super classes

Class variables can be value, reference or pointer variables

Variables can be const qualified

Class data is initialized with default construction, but this can be overridden

Base member initializer list syntax is required for certain initializations (const, reference, user-defined types and base class construction)

class Person {
   public:
   Person( ...) : initializer_list {... }
};

Destructors provide an opportunity to release memory that is not part the classes explicit variables



The following example (compiled with Visual C++ 2005 Express Edition) illustrates most of the points above. Experiment with this code and see what alternative approaches you can come up with.


 

//date.h
class Date {
// ========
public:
//Notice there is no default constructor
Date(int d, int m, int y) {day = d; month = m; year = y;}
 

private:
   int day, month, year;
};


//person.h
#include <string.h>

/* this code uses the more secure strcpy_s procedure from
string.h rather than the more traditional strcpy.
strcpy_s require an additional parameter which represents
the length of the destination string. The routine then checks
that there is enough room in the destination string to hold the
source string. The older strcpy did not check if there was
enough room in the destination string and would happly over-run
the destination memory if the source string was longer.
*/


class Person {
// ========
public:
Person(char * their_name, Date & bday, char * email = "unknown"):
name(0), email_address(0), birthday(bday), pid(pid_counter++) {

   char * temp = new char[strlen(their_name) + 1];
   strcpy_s(temp, strlen(their_name) + 1, their_name);
   name = temp;
   email_address = new char[strlen(email) + 1];
   strcpy_s(email_address, strlen(email) + 1, email);
}

Person(const Person & p):
name(0), email_address(0), birthday(p.birthday), pid(pid_counter++) {

   char * temp = new char[strlen(p.name) + 1];
   strcpy_s(temp, strlen(p.name) + 1, p.name);
   name = temp;
   email_address = new char[strlen(p.email_address) + 1];
   strcpy_s(email_address, strlen(p.name) + 1, p.email_address);
}

~Person(void) {
   delete [] (char *) name;
   delete [] email_address;

}

const char * getName() const {return name;}
int getPID() { return pid; }
const char * getEmailAddress() {return email_address;}
void setEmailAddress( const char * address ) {
   if(address) {
      delete [] email_address;
      email_address = new char[strlen(address) + 1];
      strcpy_s(email_address, strlen(address) + 1, address);
   }
}

Person & operator=(const Person & p){ //assignment operator
   if(this != &p) {

      delete [] (char *) name;
      delete [] email_address;

      char * temp = new char[strlen(p.name) + 1];
      strcpy_s(temp, strlen(p.name) + 1, p.name);
      name = temp;
      email_address = new char[strlen(p.email_address) + 1];
      strcpy_s(email_address, strlen(p.email_address) + 1, p.email_address);
      birthday = p.birthday;
   }
   return *this;
}


static int getCurrentPIDCount() {return pid_counter;}
void printOn(ostream & o) const {
   o << name << " #" << pid << " email:" << email_address;
}

private:
   const char * name;
   char * email_address;
   Date birthday;
   int pid;
   static int pid_counter;
};

int Person::pid_counter = 1000; //pid's start at 1000 and get larger

ostream & operator<<(ostream & ostr, const Person & p) {
   p.printOn(ostr);
   return ostr;
}

 


//employee.h
class Employee : public Person {
// ========
public:
Employee(char * their_name, Date & bday, Person & supervisor, char * email = "unknown"):
  Person(their_name, bday, email), manager(supervisor),
  employeeNumber(empNumberCounter++)
  { }
private:
   Person & manager;
   const int employeeNumber;
   static int empNumberCounter;
};

int Employee::empNumberCounter = 0;


//main.cpp
#include <iostream>
using namespace std;

#include "date.h"
#include "person.h"
#include "employee.h"

void main() {
   Person lou("Lou", Date(29,7,1963), "lou@chat");
   Person sue("Sue", Date(21,7,1945) );
   Employee john("John", Date(4,8,1973), sue, "john@chat");
   cout << lou <<"\n" << sue <<"\n" << john << "\n";
}



Why we need to write our own copy constructor and assignment operator
The following series of slides illustrates why we often need to write our own copy constructor and assignment operator for classes we build. This happens whenever the class has pointer or reference variables, especially if they refer to heap objects created with new.










 
 



Exercise
1) What problems do you foresee writing an assignment operator or copy constructor for the Employee Class
2)What memory problems do you foresee if you don't write an appropriate assignment operator or copy constructor
 



Summary
1) data is initialized by the invocation of constructors

2) Default constructors are used to initialize memory unless other are specified in the base member initializer list

3) Base class default constructors are called unless alternatives are specified in the base member initializer list

4) const and references cannot be given an initial value with an assignment operator so they require the use of the base member initializer list (remember the difference between T x = y; and T x; x=y;

5) Assignment operators and copy constructors are likely needed for any class with pointer or reference variables, especially classes the use heap space.

6) Destructors are called automatically when an objects memory is being reclaimed, and provide an opportunity for to clean up other memory (or other resources).
 




 



Exercise
Show what the following program outputs.

#include <iostream>
using namespace std;

class A {
int n;
public:
   A() {cout <<
"A()\n";}
   A(
int i):n(i){
      cout <<
"A(int)\n";
   }
   const A & operator=(const A & a){
      n = a.n; cout <<
"A::operator=()\n"; return *this;
   }
};
 

class B {
   A a;
public:
   B(){cout <<
"B()\n";}
   B(
int i){
      a = i; cout <<
"B(int)\n";
   }
   const B & operator=(const B & b){
      a = b.a; cout <<
"B::operator=()\n"; return *this;
   }
};

int main() {
   B x(2), y;
   y = x;
   return 0;
}

 



Review Questions: (Yes or No)
 

1) When a variable is declared in a program, the memory is allocated immediately for the variable?
 

2) If a class does not define a default constructor, the compiler will always provide a default constructor?
 

3) If a class does not define a copy constructor, the compiler will always provide a copy constructor?
 

4) A constructor can be used as a type conversion function only if it has exactly one parameter?
 

5) Only operators provided by C++ can be overloaded.  Operator overloading cannot create new operators?
 

6) Two functions may have the same name if they must be called with different parameter lists, or they have different return types?
 

7) You may assign a pointer of type const int * to a pointer of type int * ?
 

8) You may assign a variable of type const int to a variable of type int ?
 

9) Whenever a class has a pointer member, a destructor must be defined in the class?
 

10) If a class has a constructor that initializes its data members, but has no default constructor defined, then you cannot define an array of objects without initialization?



Exercise

Each of the following program segments has a problem that can occur during the creation, use, or destruction of the objects. In each case circle the place in the program where the problem appears to be and write a brief explanation of the problem.

1A)
#include <iostream.h>
class Foo {
 int i;
public:
 Foo(int x) : i(x) {}
 void set(int x) {i=x;}
 void print() {cout << i;}
};

int main() {
 Foo a;
 a.set(3);
 a.print();
 return 0;
}

1B)
#include <iostream.h>
#include <string.h>

class ArrayClass {
 char * info;
 int size;
public:
 ArrayClass(char *a = NULL) : info(new char[strlen(a)+1])
  {strcpy(info, a);}
 ~ArrayClass() {delete [] info;}
 void print() const {cout << info << endl;}
};

int main() {
 ArrayClass a("a_string"), b(a);
 a.print();
 b.print();
 return 0;
}

1C)
#include <iostream.h>
#include <string.h>

class Whatever {
 int * p;
public:
 Whatever() :p(NULL) {}
 Whatever(int * q) :p(q) {}
 ~Whatever() {delete p;}
 void print() const {cout << *p;}
};

int main() {
 int i=3, *q = &i;
 Whatever a(q);
 a.print();
 return 0;
}
 



Exercise:  What does this program output
(make sure you can explain why the output is what it is)

#include <iostream>
using namespace std;

class A {
   int i;
public:
   A() {cout <<
"A()\n" ;}
   A(
int x) :i(x) {cout << "A(int)\n" ;}
   A(
const A& a) : i(a.i) {cout << "A(const A&)\n" ;}
   ~A() {cout <<
"A()\n" ;}
};

class
B {
   A a;
public:
   B() {cout <<
"B()\n" ;}
   B(
int x) :a(x) {cout << "B(int)\n" ;}
   B(
const B& b) : a(b.a) {cout << "B(const B&)\n" ;}
   ~B() {cout <<
"~B()\n" ;}
};

B func(B k) {
return k;}

int
main() {
   B b1(2), b2;
   b2=func(b1);
   return
0;
}




OLD MIDTERM QUESTION

Sample Code