Exercises

* Ex 7.5(a,b), 7.8.

Reading Assignment

* Ch 8 - 8.4.

Assignment #3

* due Fri July 14 (but you're welcome to turn in today).

Assignment #4

* a tough one.

* start early.

* due Fri July 21.

Common problems when using pointers

* Inaccessible objects

* An inaccessible object is a dynamic object without any pointer left pointing to it.

* Dangling pointers

* A pointer that still points to an object that has been deallocated.

Inaccessible objects and memory leaks

1. Example

int *p = new int;

*p = 45;

p = new int;

*p = 55;

2. Example

int *p = new int;

*p = 45;

int *q = new int;

*q = 55;

p = q;

Dangling Pointers

1. Example

int* fun()

{

int result;

...

return &result;

}

2. Example

int *p = new int;

*p = 45;

int *q = p;

delete p;

*q = 55;

Guidelines to using dynamic data

* The guidelines here are slightly different from the text.

1. Before invoking new, see if the associated pointer variable already points to an object. If so, is the object pointed to by someone else or should it be deallocated?

2. The new operator returns a pointer to an object. If the object's type is a class, an appropriate constructor has been called. If not, the object is still unassigned.

3. Before dereferencing a pointer, be sure that the pointer is assigned and not NULL.

4. After invoking delete, remember the associated pointer variable is now considered unassigned.

5. When assigning a value to a pointer variable, be certain that it does not already point to an object that will then become inaccessible.

6. When a function contains automatic local variables, the address of any one of them, if returned, is a dangling pointer.

A Vector Module

* In C++, built in arrays are indispensable data structures but have several short comings:

1. When declaring an array, the programmer must state its size in advance.

2. Out-of-bounds subscripts are not detected at run time.

int v[20];

v[22] = 1100; // Oh No!

3. There is no aggregate assignment of one entire array to another.

4. Arrays cannot be returned as function values.

* These can be overcome by using a C++ class.

Specification of a Vector module

* We would like to have a class that allows us to do something like this:

Int_Vector u(10); // a 10-element vector

cout << "Input array size: ";

cin >> n;

Int_Vector v(n); // a n-element vector

u[2] = v[3]; // need subscript [] operator

Int_Vector w;

w = v; // assigning an entire vector

Int_Vector x(w); // copy constructor

The Int_Vector class

class Int_Vector {

public:

Int_Vector(int n = 1);

// constructor, also default constructor

Int_Vector(const Int_Vector& other);

// copy constructor

void operator = (const Int_Vector& other);

// assignment operator

int& operator [] (int i) const;

// subscript operator

~Int_Vector();

// destructor

private:

int* elem;

int size;

};

* The constructor

Int_Vector::Int_Vector(int n)

{

if (n < 1) {

cerr << "appropriate error message\n";

size = 1;

} else {

size = n;

}

elem = new int[size];

}

Destructors

* A destructor is implicitly invoked when the class instance is destroyed.

* A class instance is destroyed when it "goes out of scope", i.e., when the control exits the block in which it is declared.

Int_Vector iv1;

while (...) {

Int_Vector iv2;

for (...) {

Int_Vector iv3;

...

} // iv3 destroyed

...

} // iv2 destroyed, destructor invoked.

...

* If you don't have pointers in your data members, then you don't need to write a destructor.

* Space allocated in the call stack will be deallocated when functions terminate.

* Space allocated in the heap needs to be explicitly reclaimed.

Int_Vector::~Int_Vector()

{

delete [] elem;

}

* There should only be ONE destructor in a class.

* The destructor does not take any arguments.

* Use [ ] for an array of instances. That will ensure that the destructor is called for each array element.

* In our example, the array element is "int", it is still a good idea to get into the habit of using [ ].

Overloading the subscript operator

int& Int_Vector::operator [] (int i) const

{ assert(i < 0 || i >= size); // range check

return elem[i];

}

* This would allow clients to write code like the following:

Int_Vector a(3);

Int_Vector b(4);

a[2] = b[3];

* Why is it necessary to return "int&"?

* What is the restriction of its usage if it returns "int" instead of "int&"?

Overloading the assignment operator

* C++ provides a free assignment operator if you don't write one.

* That assignment operator will make a shallow copy of the argument. (v1 = v2;)

Problems with shallow copying

* Changing an element of one Int_Vector instance changes the other one.

Deep copying

void // can you think of another return type?

Int_Vector::operator = (const Int_Vector& other)

{

assert(size == other.size);

for (int i = 0; i < size; i++) {

elem[i] = other.elem[i];

}

}

Copy constructors

* The same problem with deep versus shallow copying also can appear in this context:

1. Int_Vector iv1 = iv2;

2. passing a copy of an actual parameter to a formal parameter.

3. returning a class instance as the value of a function:

return some_Int_Vector_instance;

* By default, C++ performs such initialization using shallow copy semantics.

* To define your way of copying, you can write a copy constructor.

Int_Vector::Int_Vector(const Int_Vector& other)

{

size = other.size;

elem = new int [size];

for (int i = 0; i < size; i++) {

elem[i] = other.elem[i];

}

}

* This function is similar to the assignment operator.

* What is the major difference?

Design C++ classes

* If a class allocates and deallocates data on the free store, it almost certainly needs the following suite of member functions to ensure deep copying of dynamic data:

1. constructor - to create data on the free store.

2. copy constructor - for deep copying in initialization.

3. destructor - to release memory back to the free store.

4. assignment operator - for deep copying in assignments.

Exercise

* Assume we forget to use deep copying for the Int_Vector class,

Int_Vector iv1(3);

iv1[0] = 100;

iv1[1] = 200;

iv1[2] = 300;

Int_Vector iv2(iv1);

iv2[0] = 1000; // what does iv1 contain now?

Int_Vector iv3(3);

iv3 = iv2;

iv3[1] = 2000; // what does iv1 contain now?

iv3[2] = iv1[0];// what does iv1 contain now?

iv3[3] = iv1[1];// why is there an error here?