• No results found

Memory Management in C ++

6.5 CREATING AN ARRAY CLASS

We now propose and develop a C++ class that models complex arrays. In particular, we encapsulate much of the nitty-gritty details in easy-to-use member functions. These functions hide how the code is actually realised, thus making life easier for the programmer. The ability to hide data and code details is one of the main advantages of the object-oriented paradigm.

We now discuss how we have implemented this C++ class.

6.5.1 Memory allocation and deallocation

In the first place we need to create arrays. This is done using constructors. We have defined the default constructor in the private region and this means that it cannot be used by client code.

We create an array of a given size and we also can create an array as a copy of another one (the copy constructor).

The member data is given as follows:

private:

Complex* arr;

int size;

The declaration for the public constructors are:

ComplexArray(int size);

ComplexArray(const ComplexArray& source);

The body of these constructors is given by:

// Constructor with size

ComplexArray::ComplexArray(int Size) {

arr=new Complex[size];

size=Size;

}

// Copy constructor

ComplexArray::ComplexArray(const ComplexArray& source) {

// Deep copy source size=source.size;

arr=new Complex[size];

for (int i=0; i<size; i++) arr[i]=source.arr[i];

}

The destructor for this class has a non-zero body. It must be declared using the virtual specifier (we shall explain why in a later chapter):

virtual ∼ComplexArray();

The body of the destructor is given by:

ComplexArray::∼ComplexArray() {

delete[] arr;

}

Please note that the virtual specifier should only be used once, namely in the header file, otherwise you will get a compiler error.

6.5.2 Accessing functions

Once we have created an array we now need some way of accessing and modifying its elements.

To this end, we use the overloaded indexing operator []. Furthermore, in order to avoid range errors, we have created two member functions that return the smallest and largest indices in the array:

int MinIndex() const; // Smallest index in array int MaxIndex() const; // Largest index in array The indexing operator is defined by:

const Complex& operator[](int index) const;

Complex& operator[](int index);

Here is a good example on how to use the indexing operator and ensuring that the arrays in your code remain within their legitimate bounds:

Complex ComplexSum(const ComplexArray& carr, int n) { // Complex function of several complex variables

Complex sum = carr[carr.MinIndex()];

for(int j = carr.MinIndex() + 1; j <= carr.MaxIndex(); j++) {

sum += carr[j];

}

return sum;

}

Note that we have not included code for exception handling. That’s the stuff of another chapter.

The advice at this stage is to use the two member functions above for the smallest and largest indices in the array.

6.5.3 Examples

We now give a simple example on how to use the array. It is much easier than before, for example:

ComplexArray fixedArray(5);

for (int i=fixedArray.MinIndex();i<=fixedArray.MaxIndex(); i++) {

fixedArray[i] = Complex ((double) i, 0.0);

}

The difference now is that we state what we want to do and not how we want to do it.

Furthermore, when the array object goes out of scope its destructor is called whereby the array’s memory is cleaned up.

6.5.4 The full header file

We now assemble the declaration of member functions of the class ComplexArray for easy reference, on the one hand, and we give an example of what the author considers to be a good C++ style in terms of readability and reliability, on the other hand.

// ComplexArray.hpp //

// Simple Complex Array class.

//

// (C) Datasim Education BV 1995-2006

#ifndef ComplexArray hpp

#define ComplexArray hpp

#include "Complex.hpp"

class ComplexArray {

private:

Complex* arr;

int size;

public:

// Constructors & destructor ComplexArray();

ComplexArray(int size);

ComplexArray(const ComplexArray& source);

virtual ∼ComplexArray();

// Selectors int Size() const;

int MinIndex() const; // Smallest index in array int MaxIndex() const; // Largest index in array // Operators

const Complex& operator[](int index) const;

Complex& operator[](int index);

ComplexArray& operator = (const ComplexArray& source);

};

#endif // ComplexArray hpp

6.6 SUMMARY AND CONCLUSIONS

We have introduced the vitally important topic of memory management. In particular, we discussed the problem of explicitly allocating and deallocating memory on the heap. This is a topic that tends to be glossed over. To this end, we hope that this chapter (and Chapter 22) will dispel the bogeyman of ‘blue screen’ and infamous memory leaks.

In order to promote relevance to the current reader group, we have taken an example (a C++

class that models arrays of complex numbers) that has a number of applications to financial engineering.

In later chapters we shall show how to hide low-level memory management details in easy-to-use classes. This is an example of the Information Hiding principle.

6.7 EXERCISES

1. In this exercise we create a two-dimensional matrix structure containing 10 rows and 20 columns (for example). You can create the matrix using static or dynamic memory or even by combining the two forms, for example:

// Compile-time

Complex matrix [10][20];

// Run-time (pointers to pointers) Complex** matrix2;

// "Hybrid": pointers to fixed arrays Complex* matrix3[10];

Create code to initialise each of the above variables. Each element in the matrices should have the default value Complex (1.0, 0.0). Create code to print the contents of each of these matrices.

Pay attention to allocation and deallocation of memory if you are using pointers!

2. Let us suppose that we wish to write a global function that we call MySwap that interchanges the values of two complex numbers. Consider the following alternatives by examining their

function prototypes:

void MySwap(Complex c1, Complex c2); // Call by value

void MySwap2(Complex& c1, Complex& c2); // Call by reference

void MySwap3(const Complex& c1, const Complex& c2); // const references void MySwap4(Complex* c1, Complex* c2); // Using pointers

void MySwap5(const Complex* c1, const Complex* c2); // const pointers Write the bodies of each of these functions and test the functions to see what happens. What happens? What goes wrong and what goes right? Which solution do you approve of most and why?

3. A C++ struct is similar to a class in C++ except that all members are public. In fact, they are used as a kind of data container although they also may have member functions.

Consider the structure for a two-dimensional point:

struct Point {

double x; // x coordinate double y; // y coordinate }

Now define an array of Point instances representing the unit rectangle (0,0), (1,0), (0,1) and (1,1). Consider using structs and dynamic arrays (for example, you like to create a Polylineat a later stage; a polyline is a dynamic array of points).

4. Modify and adapt the code in ComplexArray to create a data container representing a bounded stack of doubles. In general, it is possible to create a stack of a given size and then it is possible to push a double onto the stack and it is also possible to pop the most-recent value from the stack (we call stack a LIFO == Last In First Out structure). The interface is:

class Stack {

private:

// EXX What is the member data in this case?

Stack(const Stack& source); // Copy constructor // Operators

Stack& operator = (const Stack& source);

public:

// Constructors & destructor

Stack(); // Default constructor

Stack(int NewSize); // Initial size of the stack virtual ∼Stack(); // Destructor

void Push(double NewItem); // Push element onto stack

double Pop(); // Pop last pushed element

};

#endif // STACK HPP

Write the body of each member function and write a test program. Again, take care of memory management (new in constructors and delete in the destructor).

5. Let us have a look at matrices with 10 rows and 20 columns again. To this end, examine the following code for motivation:

#include <iostream>

using namespace std;

int main() {

double m[10][20];

m[0][1] = 2.0;

// etc.

cout << m[0][1] << endl;

return 0;

}

Write global functions for:

(a) adding, subtracting and multiplying matrices (b) multiplication of a matrix by a scalar

(c) the minimum, maximum and average values of a matrix (d) sum of squares of the elements of a matrix

Create a test program and run it.