31
CPSC 252 The Big Three Page 1 The “Big Three” Every class that has data members pointing to dynamically allocated memory must implement these three methods: • copy constructor • destructor • overloaded assignment operator Do not rely on the default versions provided by the compiler! We will discuss these using the IntVector class as an example • make sure you study the full implementation on the course website in detail • the labs and assignments require similar

CPSC 252 The Big Three Page 1 The “Big Three” Every class that has data members pointing to dynamically allocated memory must implement these three methods:

Embed Size (px)

Citation preview

CPSC 252 The Big Three Page 1

The “Big Three”

Every class that has data members pointing to dynamically allocated memory must implement these three methods:

• copy constructor

• destructor

• overloaded assignment operator

Do not rely on the default versions provided by the compiler!

We will discuss these using the IntVector class as an example

• make sure you study the full implementation on the course website in detail

• the labs and assignments require similar designs

CPSC 252 The Big Three Page 2

Dynamic memory version of IntVector (I)

class IntVector// Invariant: there are MAX_SIZE entries// indexed from 0 to MAX_SIZE-1// entries not defined unless initialized{private: static const int MAX_SIZE=100; int capacity; int* value; // dynamic array of integer values

Changes to the implementation to provide more flexibility• array size (capacity) is now a variable, not a constant• there is a default size• value is a pointer to an array, not an array

CPSC 252 The Big Three Page 3

Constructor for dynamic memory IntVector

We have to declare a constructor in IntVector.h

IntVector( void );// Post: vector has a capacity of MAX_SIZE integers

We have to define the constructor in IntVector.cpp

IntVector::IntVector( void )// Post: vector has a capacity of MAX_SIZE integers{ capacity = MAX_SIZE; value = new int[ MAX_SIZE ];}

None of the entries are defined, but memory is allocated

CPSC 252 The Big Three Page 4

Making copies of objects

A copy constructor is invoked:

• implicitly when an object is a call-by-value parameter

IntVector myVector;

void myFunc( IntVector someVector ){ … }

• explicitly when we declare an object to be a copy

IntVector otherVector( myVector );

If we do not implement a copy constructor for a class, the compiler implements one for us:

• it makes a shallow “bit-wise” copy of each data member

• this works for simple classes, with no dynamic memory

CPSC 252 The Big Three Page 5

What goes wrong with shallow copies

This code does not do what you think it does!

IntVector myVector;

myVector.at(0) = 27; myVector.at(2) = -32;

IntVector myOtherVector( myVector );

myOtherVector.at(2) = 12;

cout << myVector.at(2) << “ “ << myOtherVector.at(2);

The two objects share the same dynamic memory• the capacities and the pointers have been copied

myVector myOtherVector

27

12

value

capacity

value

capacity 4 4

CPSC 252 The Big Three Page 6

Deep copies instead of shallow copies

We have to do more work for a deep copy:

• don’t copy the pointers

• copy the objects to which the pointers are pointing instead

• then we get different data for each copy

• subsequent changes to one do not affect the other

myVector myOtherVector

27

12

value

capacity

value

capacity 4

27

-324

CPSC 252 The Big Three Page 7

Declaring a copy constructor:

IntVector( const IntVector& otherVector );// Post: this vector is a copy of otherVector

Note that the copy constructor:

• has the same name as the class (like all constructors)

• it does not specify a return type

• the parameter is passed by reference (this is essential)

• call-by-value would require the copy constructor!

• the parameter is const

Most compilers generate an error if you attempt to use a value parameter when declaring a copy constructor

CPSC 252 The Big Three Page 8

Defining (implementing) a copy constructor

We make a deep copy by:

• allocating new memory for the copy

• copying the values from the old memory to the new

IntVector::IntVector( const IntVector& otherVector )// Post: this vector is a copy of otherVector

{

capacity = otherVector.capacity;

value = new int[ capacity ];

for ( int i = 0; i < capacity; i++ )

{

value[i] = otherVector.value[i];

}

}

CPSC 252 The Big Three Page 9

The copy() helper function

For almost all classes that have a copy constructor

• we do the same operations for an assignment operator

• so we make a helper function that does the work

void IntVector::copy( const IntVector& otherVector )// Post: this vector is a copy of otherVector

{

capacity = otherVector.capacity;

value = new int[ capacity ];

for ( int i = 0; i < capacity; i++ )

{

value[i] = otherVector.value[i];

}

}

CPSC 252 The Big Three Page 10

Our final version of the copy constructor

We simply use the copy() helper function to implement the copy constructor

IntVector::IntVector( const IntVector& otherVector )// Post: this vector is a copy of otherVector

{

copy( otherVector);

}

All of the classes that we will see will adopt this convention• there is a copy helper function (always private)• the copy constructor and the assignment operator invoke it

CPSC 252 The Big Three Page 11

Why we need destructors

void myFunc( void ){ IntVector myVector();

// do whatever with the vector

}

The lifetime of myVector is only the function invocation

• memory is allocated when myFunc() is called

• memory must be released when myFunc() returns

• unfortunately, the memory is not automatically released

• we need to implement a destructor member function

• this will release the memory and whatever else needs doing

CPSC 252 The Big Three Page 12

Declaration of the destructor for the IntVector class

~IntVector( void );// Post: memory allocated to vector has been released

• destructors do not have a return type

• the name of a destructor is always the name of the class preceded by a “~” (tilde)

• destructors perform any necessary “clean-up” operations

• we use a helper function (we will use it again later)

IntVector::~IntVector( void )// Post: memory allocated to vector has been released{

clear();

}

CPSC 252 The Big Three Page 13

The clear() helper function

For almost all classes that have a destructor:

• we need the same operations for an assignment operator

• so we make a helper function that does the work

void IntVector::clear( void )// Post: this vector has no dynamic memory allocated

{

delete [] value;

}

• we use [] even though it is not necessary for integers

• it would be necessary if elements require destructors

• always private (just like the copy() helper function)

CPSC 252 The Big Three Page 14

Assignment statements for objects

The assignment operator “=” can be used for all objects

• it can always be applied to any object

• it can be explicitly declared in the class declaration

IntVector& operator=( const IntVector& otherVector );// Post: this vector is a copy of otherVector

• otherwise the compiler will provide a default version

• the assignment operator is overloaded if we explicitly define it for a class

• the default version may be all we need

• but often it will not be so we have to provide our own

CPSC 252 The Big Three Page 15

Default component-wise assignment operatorclass CSample{public: CSample( void );

int getData( void ) const; void setData( int data );

private: int m_data;};

CSample sample1, sample2;

sample1.setData( 7 );sample2 = sample1;cout << sample2.getData();

The default version of the assignment operator uses the data members in sample1 to initialize the corresponding data members in sample2

CPSC 252 The Big Three Page 16

Syntax for the assignment operator

The declaration of the overloaded assignment operator for our IntVector class looks like this:

IntVector& operator=(const IntVector& otherVector );// Post: this vector is a copy of otherVector

• the return value is a reference to an IntVector

• the IntVector parameter is passed by reference

• the parameter is const

• the return value is not const

• the return value is not void

Why were these choices made?

CPSC 252 The Big Three Page 17

A reference is returned by the assignment A reference is returned by the assignment operatoroperator

IntVector & operator=( const IntVector& otherVector );IntVector & operator=( const IntVector& otherVector );// Post: this vector is a copy of otherVector// Post: this vector is a copy of otherVector

• The “=” operator is a binary operator

• it has a left operand and a right operand vector2 = vector1;

• it returns a reference to the left operand

• often we do not make use of this, but we might vector3 = vector2 = vector1;

• the assignment operator is right associative: vector3 = ( vector2 = vector1 );

• the result is the right operand for the second assignment

CPSC 252 The Big Three Page 18

A const reference is passed to the assignment A const reference is passed to the assignment operatoroperator

IntVector& operator=( const IntVector & otherVector );IntVector& operator=( const IntVector & otherVector );// Post: this vector is a copy of otherVector// Post: this vector is a copy of otherVector

We don’t want to change the right operand

• so it is a const parameter

We don’t want to make a copy of the right operand

• so it is a reference parameter

We do want to change the left operand

• so the method is not const

CPSC 252 The Big Three Page 19

The keyword this

• this can be used in any member function (method)

• it provides a pointer to the object that invoked the method

• we can think of *this as being the object

• which operand invokes the assignment operator?

vector2 = vector1;

• by convention, the left operand invokes the method

• the assignment above is equivalent to the method call

vector2.operator=( vector1 );

CPSC 252 The Big Three Page 20

Definition (implementation) for assignment

IntVector& IntVector::operator=( const IntVector& otherVector )

{ if ( &otherVector != this ) { clear(); copy( otherVector ); } return *this;}

• special case for left=right (nothing to do)

• if-test assumes different objects have different addresses

• if not the same object “destroy” the left operand

• then make a copy of the right operand

CPSC 252 The Big Three Page 21

What else besides the “Big Three”?

We have now seen all of the “big three” methods

• copy constructor

• destructor

• overloaded assignment operator

There are some other methods that we usually need, but these are “easy” once we have the “big three”

• parameterized constructor

• default constructor

• accessor methods and mutator methods

• other helper methods (often a grow() method)

CPSC 252 The Big Three Page 22

Dynamic memory version of IntVector (review)

class IntVector// Invariant: there are MAX_SIZE entries// indexed from 0 to MAX_SIZE-1// entries not defined unless initialized{private: static const int MAX_SIZE=100; int capacity; int* value; // dynamic array of integer values

public: // method declarations go here…

CPSC 252 The Big Three Page 23

Parameterized constructor

We want to specify the size of an IntVector at run-time

int capacity;cout << “How many integers do you want to store? ”;cin >> capacity;IntVector myVector( capacity );

We provide a constructor that accepts the capacity as a parameter

IntVector( int numInts );// Pre: numInts > 0// Post: vector has capacity numInts integers

CPSC 252 The Big Three Page 24

Implementing a parameterized constructor

IntVector::IntVector( int numInts )// Pre: numInts > 0// Post: vector has capacity numInts integers{ capacity = numInts; value = new int[ numInts ];}

• array is initialized to specified size

• the object remembers the size

• none of the elements are initialized

CPSC 252 The Big Three Page 25

Formal parameter names

IntVector::IntVector( int capacity )// Pre: capacity > 0// Post: vector will hold capacity integers{ capacity = capacity ; value = new int[ capacity ];}

• OK if name does not match declaration name

• OK to be the same as a member variable name

• but the above code will not work, instead we need to say

{ this->capacity = capacity; value = new int[ capacity ];}

CPSC 252 The Big Three Page 26

Default constructor

The default constructor has no parameters

IntVector( void );// Post: vector has a capacity of MAX_SIZE integers

We have already seen how to implement this

IntVector::IntVector( void )// Post: vector has a capacity of MAX_SIZE integers{ capacity = MAX_SIZE; value = new int[ MAX_SIZE ];}

CPSC 252 The Big Three Page 27

What classes get for free from the compiler

The compiler provides the following “for free”

• a default component-wise assignment operator

• a default component-wise copy constructor

The compiler may also provide

• a default default constructor

• only if no constructors are declared

• if you declare a parameterized constructor

• or if you declare a copy constructor

• then there is no default default constructor

This is for your “own protection”

CPSC 252 The Big Three Page 28

What else do we need for the IntVector class?

The change to using dynamic arrays means that:

• we had to write a default constructor and the “big three”

• but the public interface has not changed for existing methods

• so client code that is based on fixed-size arrays still works

• our new version is backwards compatible with the old

We will have to look at the implementations of the other member function

• size()- modest changes

• at() - neither version needs any change

• print() - no changes required

CPSC 252 The Big Three Page 29

int IntVector::at( int index ) const// Post: if 0 <= index < size() the element// at index is returned, otherwise program aborted{ return value[ index ];}

int& IntVector::at( int index )// Post: if 0 <= index < size() the element// at index is returned, otherwise program aborted{ return value[ index ];}

int IntVector::size( void ) const// Post: capacity of vector has been returned { return capacity ; // not always MAX_SIZE}

CPSC 252 The Big Three Page 30

How else might we extend the IntVector class?

Possible extensions:

• indices can be negative as well as positive

• the array grows as needed when elements are added

• so client code that is based on fixed-size arrays still works

• our new version is backwards compatible with the old

The version that is in /examples/IntVector has some of these

On the next slide we will see when to grow the array

CPSC 252 The Big Three Page 31

Automatically expanding the array

When we store into the array, we can expand the array (and copy existing values) if it is not big enough

int& IntVector::at( int index )// Post: if 0 <= index < size() the element// at index is returned, otherwise program aborted{ if ( capacity <= index ) grow( index ) // allocate a larger array return value[ index ];}

The helper function grow() is similar to the copy() helper• you ought to be able to write this one yourself!• the IntSet class has a grow() helper function