Dynamic Memory Allocation and Arrays of Objects in Computing and Statistical Data Analysis, Slides of Computational and Statistical Data Analysis

This document from lecture 6 of the computing and statistical data analysis course covers dynamic memory allocation using the new operator, dynamic arrays, memory leaks, dangling pointers, operator overloading, static member functions, and class templates. It also includes examples using the twovector class.

Typology: Slides

2011/2012

Uploaded on 03/08/2012

leyllin
leyllin 🇬🇧

4.3

(15)

241 documents

1 / 23

Toggle sidebar

This page cannot be seen from the preview

Don't miss anything!

bg1
Computing and Statistical Data Analysis
Lecture 6
Arrays of objects
Memory allocation: static, automatic and dynamic
Memory leaks and dangling pointers
Operator overloading
Static member functions
digression on destructors, copy constructors
Class templates
G. Cowan / RHUL 1 Computing and Statistical Data Analysis / Lecture 6
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17

Partial preview of the text

Download Dynamic Memory Allocation and Arrays of Objects in Computing and Statistical Data Analysis and more Slides Computational and Statistical Data Analysis in PDF only on Docsity!

Computing and Statistical Data Analysis

Lecture 6

Arrays of objects

Memory allocation: static, automatic and dynamic

Memory leaks and dangling pointers

Operator overloading

Static member functions

digression on destructors, copy constructors

Class templates

Dynamic arrays

An array’s name is a pointer to its first element. We can create

a “dynamic array” using the new operator:

double* array; int len; cout << "Enter array length" << endl; cin >> len; // array length set at run time array = new double[len]; ...

When we’re done (e.g. at end of function where it’s used), we need

to delete the array:

delete [] array;

Memory allocation

We have seen two main ways to create variables or objects:

(1) by a declaration (automatic memory allocation):

int i; double myArray[10]; TwoVector v; TwoVector* vPtr;

(2) using new: (dynamic memory allocation):

vPtr = new TwoVector(); // creates object TwoVector* uPtr = new TwoVector(); // on 1 line double* a = new double[n]; // dynamic array float* xPtr = new float(3.7);

The key distinction is whether or not we use the new operator.

Note that new always requires a pointer to the newed object.

The stack

When a variable is created by a “usual declaration”, i.e., without

new, memory is allocated on the “stack”.

When the variable goes out of scope, its memory is automatically

deallocated (“popped off the stack”).

int i = 3; // memory for i and obj MyObject obj; // allocated on the stack ... } // i and obj go out of scope, // memory freed

Deleting objects

To prevent the memory leak, we need to deallocate the object’s

memory before it goes out of scope:

MyClass* ptr = new MyClass(); // creates an object MyClass* a = new MyClass[n]; // array of objects ... delete ptr; // deletes the object pointed to by ptr delete [] a; // brackets needed for array of objects }

For every new, there should be a delete.

For every new with brackets [], there should be a delete [].

This deallocates the object’s memory. (Note that the pointer to the

object still exists until it goes out of scope.)

Dangling pointers

Consider what would happen if we deleted the object, but then still

tried to use the pointer:

MyClass* ptr = new MyClass(); // creates an object ... delete ptr; ptr->someMemberFunction(); // unpredictable!!!

After the object’s memory is deallocated, it will eventually be

overwritten with other stuff.

But the “dangling pointer” still points to this part of memory.

If we dereference the pointer, it may still give reasonable behaviour.

But not for long! The bug will be unpredictable and hard to find.

Some authors recommend setting a pointer to zero after the delete.

Then trying to dereference a null pointer will give a consistent error.

Operator overloading

Suppose we have two TwoVector objects and we want to add them.

We could write an add member function:

TwoVector TwoVector::add(TwoVector& v){ double cx = this->m_x + v.x(); double cy = this->m_y + v.y(); TwoVector c(cx, cy); return c; }

To use this function we would write, e.g.,

TwoVector u = a.add(b);

It would be much easier if would could simply use a+b, but to do

this we need to define the + operator to work on TwoVectors.

This is called operator overloading. It can make manipulation of

the objects more intuitive.

Overloading an operator

We can overload operators either as member or non-member

functions. For member functions, we include in the class

declaration:

class TwoVector { public: ... TwoVector operator+ (const TwoVector&); TwoVector operator- (const TwoVector&); ...

Instead of the function name we put the keyword operator

followed by the operator being overloaded.

When we say a+b, a calls the function and b is the argument.

The argument is passed by reference (quicker) and the declaration

uses const to protect its value from being changed.

Overloaded operators: asymmetric arguments

Suppose we want to overload * to allow multiplication of a

TwoVector by a scalar value:

TwoVector TwoVector::operator* (double b) { double cx = this->m_x * b; double cy = this->m_y * b; TwoVector c(cx, cy); return c; }

Given a TwoVector v and a double s we can say e.g. v = v*s;

But how about v = s*v; ???

No! s is not a TwoVector object and cannot call the appropriate

member function (first operand calls the function).

We didn’t have this problem with + since addition commutes.

Overloading operators as non-member functions

We can get around this by overloading * with a non-member

function.

We could put the declaration in TwoVector.h (since it is related

to the class), but outside the class declaration.

We define two versions, one for each order:

TwoVector operator* (const TwoVector&, double b); TwoVector operator* (double b, const TwoVector&);

For the definitions we have e.g. (other order similar):

TwoVector operator* (double b, const TwoVector& a) { double cx = a.x() * b; double cy = a.y() * b; TwoVector c(cx, cy); return c; }

A different “static”: static members

Sometimes it is useful to have a data member or member function

associated not with individual objects but with the class as a whole.

An example is a variable that counts the number of objects of a

class that have been created.

These are called static member functions/variables (yet another use

of the word static -- better would be “class-specific”). To declare:

class TwoVector { public: ... static int totalTwoVecs(); private: static int m_counter; ... };

Static members, continued

Then in TwoVector.cc (note here no keyword static):

int TwoVector::m_counter = 0; // initialize TwoVector::TwoVector(double x, double y){ m_x = x; m_y = y; m_counter++; // in all constructors } int TwoVector::totalTwoVecs() { return m_counter; }

Now we can count our TwoVectors. Note the function is called

with class-name:: and then the function name. It is connected to

the class, not to any given object of the class:

TwoVector a, b, c; int vTot = TwoVector::totalTwoVecs(); cout << vTot << endl; // prints 3

Oops #2: digression on copy constructors

The totalTwoVec function still doesn’t work very well, since we

should count an extra TwoVector object when, e.g., we say

TwoVector v; // this increments m_counter TwoVector u = v; // oops, m_counter stays same

When we create/initialize an object with an assignment statement,

this calls the copy constructor, which by default just makes a copy.

We need to write our own copy constructor to increment

m_counter. To declare (together with the other constructors):

TwoVector(const TwoVector&); // unique signature

It gets defined in TwoVector.cc :

TwoVector(const TwoVector& v) { m_x = v.x(); m_y = v.y(); m_counter++; }

Class templates

We defined the TwoVector class using double variables. But in

some applications we might want to use float.

We could cut/paste to create a TwoVector class based on floats

(very bad idea -- think about code maintenance).

Better solution is to create a class template, and from this we

create the desired classes.

template // T stands for a type class TwoVector { public: TwoVector(T, T); // put T where before we T x(); // had double T y(); ... };