Copying Data

Steven J. Zeil

Last modified: Dec 26, 2016
Contents:

Copying data is one of the most common operations in C++.

But, First…

A slight diversion …

1 Destructors

Destructors are used to clean up objects that are no longer in use.

1.1 Destructors are Never Called Explicitly

The compiler generates all calls to destructors implicitly


Implicit Destructor Call 1

If we write

void foo (int d, int c)
{
  Money m (d,c);
  for (int i = 0; i < 100; ++i)
    {
       Money mon2 = m;
        ⋮
    }
}

the compiler generates

void foo (int d, int c)
{
  Money m (d,c);
  for (int i = 0; i < 100; ++i)
    {
       Money mon2 = m;
        ⋮
       mon2.~Money();
    }
  m.~Money();
}


Implicit Destructor Call 2

If we write

void foo (int d, int c)
{
  Money* m  = new Money (d,c);
        ⋮
  delete m;
}

the compiler generates

void foo (int d, int c)
{
  Money* m  = new Money (d,c);
        ⋮
  m->~Money();
  free(m);
}


A Typical Destructor

We use destructors to clean up data “owned” by our structured types:

class BidCollection {

  int MaxSize;
  int size;
  Bid* elements; // array of bids 

public:
  BidCollection (int MaxBids = 1000);

  ~BidCollection ();
    ⋮


Implementing the Destructor

The destructor would be implemented as

BidCollection::~BidCollection()
{
  delete [] elements;
}


Compiler-Provided Destructors

If you don’t provide a destructor for a class, the compiler generates one for you automatically.


A Compiler-Generated Destructor

time.h
#ifndef TIMES_H
#define TIMES_H

#include <iostream>

/**
 * Times in this program are represented by three integers: H, M, & S, representing
 * the hours, minutes, and seconds, respecitvely.
 */


class Time {
public:
  // Create time objects
  Time(); // midnight
  Time (int h, int m, int s);

  // Access to attributes
  int getHours() const;
  int getMinutes() const;
  int getSeconds() const;

  // Calculations with time
  void add (Time delta);
  Time difference (Time fromTime);
  
  /**
   * Read a time (in the format HH:MM:SS) after skipping any
   * prior whitepace.
   */
  void read (std::istream& in);


  /**
   * Print a time in the format HH:MM:SS (two digits each)
   */
  void print (std::ostream& out) const;



  // Comparison operators
  bool operator== (const Time&) const;
  bool operator< (const Time&) const;

private:
  // From here on is hidden
  int secondsSinceMidnight;
};

inline
std::ostream& operator<< (std::ostream& out, const Time& t)
{
  t.print(out);
  return out;
}


inline
bool operator!= (const Time& t1, const Time& t2)
{
  return !(t1 == t2);
}

inline
bool operator> (const Time& t1, const Time& t2)
{
  return t2 < t1;
}

inline
bool operator<= (const Time& t1, const Time& t2)
{
  return !(t1 > t2);
}

inline
bool operator>= (const Time& t1, const Time& t2)
{
  return !(t1 < t2);
}

#endif // TIMES_H
../copying-auctionBig3/bids.h
#ifndef BIDS_H
#define BIDS_H

#include <iostream>
#include <string>
#include "time.h"
#include "money.h"


//
//  Bids Received During Auction
//

class Bid {

  std::string bidderName;
  Money amount;
  std::string itemName;
  Time bidPlacedAt;

public:

  Bid (std::string bidder, double amt, 
       std::string item, Time placedAt);

  Bid();

  // Access to attribute
  std::string getBidder() const {return bidderName;}
  double getAmount() const {return amount;}
  std::string getItem() const {return itemName;}
  Time  getTimePlacedAt() const {return bidPlacedAt;}

  // Print a bid
  void print (std::ostream& out) const;


  // Comparison operators
  bool operator== (const Bid&) const;
  bool operator< (const Bid&) const;


};

inline
std::ostream& operator<< (std::ostream& out, const Bid& b)
{
  b.print(out);
  return out;
}



#endif

Trusting the Compiler-generated Destructor

 

What would happen if we trusted the compiler-generated destructor for BidCollection?

Answer

Compiler-generated destructors are wrong when…

The compiler-generated destructor will leak memory when

Under those circumstances, the compiler-generated destructor will cause you program to leak memory.

The fix is to implement your own destructor for such classes, and explicitly clean up your mess on the heap:

void NidCollection::~BidCollection()
{
   delete [] elements;
}

2 Copy Constructors

The copy constructor for a class Foo is the constructor of the form:

Foo (const Foo& oldCopy);

2.1 Where Do We Use a Copy Constructor?

The copy constructor gets used in 5 situations:

2.2 Compiler-Generated Copy Constructors

If we do not create a copy constructor for a class, the compiler generates one for us.

2.3 Example: Bid: Compiler-Generated Copy Constructor

struct Bid {
  std::string bidderName;
  Money amount;
  std::string itemName;
  Time bidPlacedAt;

};

Bid does not provide a copy constructor, so the compiler generates one for us, just as if we had written:

struct Bid {
  std::string bidderName;
  Money amount;
  std::string itemName;
  Time bidPlacedAt;

  Bid (const Bid&);
};
  ⋮
Bid::Bid (const Bid& b)
  : bidderName(b.bidderName), amount(b.amount),
    itemName(b.itemName), bidPlacedAt(b.bidPlacedAt)
{}

and that’s probably just fine.

2.4 Example: BidCollection: Compiler-Generated Copy Constructor

struct BidCollection {
  int MaxSize;
  int size;
  Bid* elements; // array of bids 

  /**
   * Create a collection capable of holding the indicated number of bids
   */
  BidCollection (int MaxBids = 1000);

  ~BidCollection ();
  
  /**
   * Read all bids from the indicated file
   */
  void readBids (std::string fileName);
};

BidCollection does not provide a copy constructor, so the compiler generates one for us, just as if we had written:

struct BidCollection {
  int MaxSize;
  int size;
  Bid* elements; // array of bids 

  /**
   * Create a collection capable of holding the indicated number of bids
   */
  BidCollection (int MaxBids = 1000);
  BidCollection (const BidCollection&);
};
  ⋮
BidCollection::BidCollection (const BidCollection& bc)
  : MaxSize(bc.MaxSize), size(bc.size),
    elements(bc.elements)
{}

which is not good at all!


Example: BidCollection is hard to copy

To see why, suppose we had some application code:

bidCollApplication.cpp
BidCollection removeEarly (BidCollection bc, Time t)
{
  for (int i = 0; i < x.size;)
    {
     if (bc.elements[i].bidPlacedAt < t)
        removeElement (elements, size, i);
     else
        ++i;
    }
  return bc;
}
   ⋮
BidCollection afterNoonBids =
   removeEarly (bids, Time(12,0,0));

 

Assume we start with this in bids.


 

When removeEarly is called, we get a copy of bids .

BidCollection removeEarly (BidCollection bc, Time t)
{
  for (int i = 0; i < x.size;)
    {
     if (bc.elements[i].bidPlacedAt < t)
        removeElement (elements, size, i);
     else
        ++i;
    }
  return bc;
}
    ⋮
BidCollection afterNoonBids =  removeEarly (bids, Time(12,0,0));


 

removeEarly removes the first morning bid from bc.

 

Then removeEarly removes the remaining morning bid from bc.

 

The return statement makes a copy of bc, which is stored in afterNoonBids


Trouble: bids is corrupted

 

Note that we have corrupted the original collection, bids


That’s not the worst of it!

When we exit removeEarly,

BidCollection removeEarly (BidCollection bc, Time t)
{
  for (int i = 0; i < x.size;)
    {
     if (bc.elements[i].bidPlacedAt < t)
        removeElement (elements, size, i);
     else
        ++i;
    }
  return bc;
}

the destructor for BidCollection is called on bc

BidCollection::~BidCollection()
{
  delete [] elements;
}

 

Taking us from this …


What a Mess!

 

… to this.

Both collections have “dangling pointers”


Avoiding this Problem

We could

2.5 Writing a BidCollection Copy Constructor

BidCollection::BidCollection (const BidCollection& bc)
  : MaxSize (bc.MaxSize), size (bc.size)
{
  elements = new Bid[MaxSize];
  for (int i = 0; i < size; ++i)
    elements[i] = bc.elements[i];
}

Once More, With Feeling!

 

With this copy constructor in place, things should go much more smoothly.

bidCollApplication.cpp
BidCollection removeEarly (BidCollection bc, Time t)
{
  for (int i = 0; i < x.size;)
    {
     if (bc.elements[i].bidPlacedAt < t)
        removeElement (elements, size, i);
     else
        ++i;
    }
  return bc;
}
   ⋮
BidCollection afterNoonBids =
   removeEarly (bids, Time(12,0,0));

 

When removeEarly is called, we get a copy of bids.

 

removeEarly removes the first morning bid from bc.

 

Then removeEarly removes the remaining morning bid from bc.

 

The return statement makes a copy of bc, which is stored in afterNoonBids.

But our new copy constructor actually creates a new array for afterNoonBids, copying the bids from bc’s array.


Much Nicer

 

The destructor for BidCollection is called on bc

2.6 Shallow & Deep Copying

If We Never Write Our Own

If our data members do not have explicit copy constructors (and their data members do not have explicit copy constructors, and … )


Shallow vs Deep Copy

Copy operations are distinguished by how they treat pointers:


Shallow copy is wrong when…


Compiler-generated copy constructors are wrong when…

3 Assignment

Copy constructors are not the only way we make copies of data.

3.1 Compiler-Generated Assignment Ops

If you don’t provide your own assignment operator for a class, the compiler generates one automatically.


Example: BidCollection: Guess what happens

Our BidCollection class has no assignment operator, so the code below uses the compiler-generated version.

Suppose we had some application code:

bidCollAsst.cpp
  BidCollection bids;
    ⋮
  BidCollection bc;
  Time t (12, 0, 0);
  bc = bids;
  for (int i = 0; i < x.size;)
    {
     if (bc.elements[i].bidPlacedAt < t)
        removeElement (elements, size, i);
     else
        ++i;
    }

  BidCollection bc;
  Time t (12, 0, 0);
  bc = bids;
  for (int i = 0; i < x.size;)
    {
     if (bc.elements[i].bidPlacedAt < t)
        removeElement (elements, size, i);
     else
        ++i;
    }

 

Assume we start with this in bids.

 

After the assignment, we have copied bids, bit-by-bit, into bc.

bidCollAsst2.cpp
  BidCollection bc;
  Time t (12, 0, 0);
  bc = bids;
  for (int i = 0; i < x.size;)
    {
     if (bc.elements[i].bidPlacedAt < t)
        removeElement (elements, size, i);
     else
        ++i;
    }

 

We remove the first morning bid from bc.

bidCollAsst3.cpp
  BidCollection bc;
  Time t (12, 0, 0);
  bc = bids;
  for (int i = 0; i < x.size;)
    {
     if (bc.elements[i].bidPlacedAt < t)
        removeElement (elements, size, i);
     else
        ++i;
    }

 

Then remove the remaining morning bid from bc.


bids is corrupted

 

Note that we have corrupted the original collection, bids

This should look very familiar!


Avoiding this Problem

We have already seen that, for this class, we need to implement our own copy constructor:

BidCollection::BidCollection (const BidCollection& bc)
  : MaxSize (bc.MaxSize), size (bc.size)
{
  elements = new Bid[MaxSize];
  for (int i = 0; i < size; ++i)
    elements[i] = bc.elements[i];
}

For all the same reasons, we will want to implement our own assignment operator.

3.2 Implementing Assignment

BidCollection& BidCollection::operator= 
     (const BidCollection& bc)
{
  MaxSize = bc.MaxSize;
  size = bc.size;
  delete [] elements;

  elements = new Bid[MaxSize];
  for (int i = 0; i < size; ++i)
    elements[i] = bc.elements[i];

  return *this;
}

Why return this?


Self-Assignment

When we do x = x we expect that nothing really changes.

Question: What would happen if we did

bids = bids;

using this assignment operator:

BidCollection& BidCollection::operator= 
     (const BidCollection& bc)
{
  MaxSize = bc.MaxSize;
  size = bc.size;
  delete [] elements;

  elements = new Bid[MaxSize];
  for (int i = 0; i < size; ++i)
    elements[i] = bc.elements[i];

  return *this;
}

?

**Answer**

Self-Assignment – Really?

May seem an unlikely occurrence, but can often happen in a disguised form, e.g., during array processing,

bidSet[i] = bidSet[j];

Coping with Self-Assignment

To allow for this possibility, most assignment operator functions check to see if the variable on the left (this) is at the same address as the variable on the right:

BidCollection& BidCollection::operator= (const BidCollection& bc)
{
  if (this != &bc)
    {
     MaxSize = bc.MaxSize;
     size = bc.size;
     delete [] elements;

     elements = new Bid[MaxSize];
     for (int i = 0; i < size; ++i)
       elements[i] = bc.elements[i];
    }
  return *this;
}

4 The Rule of the Big 3

A Question of Trust

Notice a pattern?


The Rule of the Big 3

If you provide your own version of any one of the Big 3, you should provide your own version of all three.


Providing the Big 3

bidcollection.h
#ifndef BIDCOLLECTION_H
#define BIDCOLLECTION_H

#include "bids.h"
#include <iostream>

class BidCollection {

  int MaxSize;
  int size;
  Bid* elements; // array of bids 

public:

  /**
   * Create a collection capable of holding the indicated number of bids
   */
  BidCollection (int MaxBids = 1000);

  // Big 3
  BidCollection (const BidCollection&);
  BidCollection& operator= (const BidCollection&);
  ~BidCollection ();

  

  // Access to attributes
  int getMaxSize() const {return MaxSize;}

  int getSize() const {return size;}



  // Access to individual elements

  const Bid& get(int index) const {return elements[index];}



  // Collection operations


  void addInTimeOrder (const Bid& value);
  //  Adds this bid into a position such that 
  //   all bids are ordered by the time the bid was placed
  //Pre: getSize() < getMaxSize()



  void remove (int index);
  // Remove the bid at the indicated position
  //Pre: 0 <= index < getSize()


  /**
   * Read all bids from the indicated file
   */
  void readBids (std::string fileName);

  // Print the collection
  void print (std::ostream& out) const;


  // Comparison operators
  bool operator== (const BidCollection&) const;
  bool operator< (const BidCollection&) const;

};


inline
std::ostream& operator<< (std::ostream& out, const BidCollection& b)
{
  b.print(out);
  return out;
}

#endif
bidcollection.cpp
#include "bidcollection.h"
#include "arrayUtils.h"
#include <fstream>

using namespace std;


/**
 * Create a collection capable of holding the indicated number of bids
 */
BidCollection::BidCollection (int MaxBids)
  : MaxSize(MaxBids), size(0)
{
  elements = new Bid [MaxSize];
}



// Big 3

BidCollection::BidCollection (const BidCollection& bc)
  : MaxSize(bc.MaxSize), size(bc.size)
{
  elements = new Bid [MaxSize];
  for (int i = 0; i < size; ++i)
    elements[i] = bc.elements[i];
}


BidCollection& BidCollection::operator= (const BidCollection& bc)
{
  if (this != &bc)
    {
      delete [] elements;
      size = bc.size;
      MaxSize = bc.MaxSize;
      elements = new Bid [MaxSize];
      for (int i = 0; i < size; ++i)
	elements[i] = bc.elements[i];
    }
  return *this;
}

BidCollection::~BidCollection ()
{
  delete [] elements;
}



// Collection operations


void BidCollection::addInTimeOrder (const Bid& value)
//  Adds this bid into a position such that 
//   all bids are ordered by the time the bid was placed
//Pre: getSize() < getMaxSize()
{
  // Make room for the insertion
  int toBeMoved = size - 1;
  while (toBeMoved >= 0 && 
	 value < elements[toBeMoved]) {
    elements[toBeMoved+1] = elements[toBeMoved];
    --toBeMoved;
  }
  // Insert the new value
  elements[toBeMoved+1] = value;
  ++size;  
}


void BidCollection::remove (int index)
// Remove the bid at the indicated position
//Pre: 0 <= index < getSize()
{
  removeElement (elements, size, index);
}

/**
 * Read all bids from the indicated file
 */
void BidCollection::readBids (std::string fileName)
{
  size = 0;
  ifstream in (fileName.c_str());
  int nBids;
  in >> nBids;
  for (int i = 0; i < nBids; ++i)
    {
      char c;
      
      string bidderName;
      double amount;
      in >> bidderName >> amount;
      
      Time bidPlacedAt;
      bidPlacedAt.read (in);
      
      string word, line;
      in >> word; // First word of itemName
      getline (in, line); // rest of item name
      
      string itemName = word + line;
      addInTimeOrder (Bid (bidderName, amount, itemName, bidPlacedAt));
    }
}


// Print the collection
void BidCollection::print (std::ostream& out) const
{
  out << size << "/" << MaxSize << "{";
  for (int i = 0; i < size; ++i)
    {
      out << "  ";
      elements[i].print (out);
      out << "\n";
    }
  out << "}";
}


  // Comparison operators
bool BidCollection::operator== (const BidCollection& bc) const
{
  if (size == bc.size)
    {
      for (int i = 0; i < size; ++i)
	if (!(elements[i] == bc.elements[i]))
	  return false;
      return true;
    }
  else
    return false;
}


bool BidCollection::operator< (const BidCollection& bc) const
{
  if (size == bc.size)
    {
      for (int i = 0; i < size; ++i)
	{
	  if (elements[i] < bc.elements[i])
	    return true;
	  else if (bc.elements[i] < elements[i])
	    return false;
	  // else keep going
	}

      // All of the elements are equal
      return false;
    }
  else
    return size < bc.size;
}
biddercollection.h
#ifndef BIDDERCOLLECTION_H
#define BIDDERCOLLECTION_H

#include "bidders.h"
#include <iostream>

//
//  Bidders Registered for auction
//


class BidderCollection {

  int MaxSize;
  int size;
  Bidder* elements; // array of items 

public:

  /**
   * Create a collection capable of holding the indicated number of items
   */
  BidderCollection (int MaxBidders = 1000);

  // Big 3
  BidderCollection (const BidderCollection&);
  BidderCollection& operator= (const BidderCollection&);
  ~BidderCollection ();

  

  // Access to attributes
  int getMaxSize() const {return MaxSize;}

  int getSize() const {return size;}



  // Access to individual elements

  Bidder& get(int index) {return elements[index];}



  // Collection operations


  int add (const Bidder& value);
  //  Adds this bidder to the collection at an unspecified position.
  //  Returns the position where added.
  //Pre: getSize() < getMaxSize()



  void remove (int index);
  // Remove the item at the indicated position
  //Pre: 0 <= index < getSize()


  int findBidder (std::string name) const;
  // Returns the position where a bidde mathcing the given
  // name can be found, or getSize() if no bidder with that name exists.



  /**
   * Read all items from the indicated file
   */
  void readBidders (std::string fileName);

  // Print the collection
  void print (std::ostream& out) const;


  // Comparison operators
  bool operator== (const BidderCollection&) const;
  bool operator< (const BidderCollection&) const;


};

inline
std::ostream& operator<< (std::ostream& out, const BidderCollection& b)
{
  b.print(out);
  return out;
}

#endif



biddercollection.cpp
#include <iostream>
#include "arrayUtils.h"
#include <fstream>

#include "biddercollection.h"

using namespace std;


/**
 * Create a collection capable of holding the indicated number of items
 */
BidderCollection::BidderCollection (int MaxBidders)
  : MaxSize(MaxBidders), size(0)
{
  elements = new Bidder [MaxSize];
}


// Big 3

BidderCollection::BidderCollection (const BidderCollection& bc)
  : MaxSize(bc.MaxSize), size(bc.size)
{
  elements = new Bidder [MaxSize];
  for (int i = 0; i < size; ++i)
    elements[i] = bc.elements[i];
}


BidderCollection& BidderCollection::operator= (const BidderCollection& bc)
{
  if (this != &bc)
    {
      delete [] elements;
      size = bc.size;
      MaxSize = bc.MaxSize;
      elements = new Bidder [MaxSize];
      for (int i = 0; i < size; ++i)
	elements[i] = bc.elements[i];
    }
  return *this;
}

BidderCollection::~BidderCollection ()
{
  delete [] elements;
}




// Collection operations


int BidderCollection::add (const Bidder& value)
//  Adds this bidder
//Pre: getSize() < getMaxSize()
{
  addToEnd (elements, size, value);
  return size - 1;
}


void BidderCollection::remove (int index)
// Remove the bidder at the indicated position
//Pre: 0 <= index < getSize()
{
  removeElement (elements, size, index);
}




/**
 * Read all bidders from the indicated file
 */
void BidderCollection::readBidders (std::string fileName)
{
    size = 0;
    ifstream in (fileName.c_str());
    int nBidders;
    in >> nBidders;
    for (int i = 0; i < nBidders && i < MaxSize; ++i)
    {
      string nme;
      double bal;
      in >> nme >> bal;
      Bidder bidder (nme, bal);;
      add (bidder);
    }
}


/**
 * Find the index of the bidder with the given name. If no such bidder exists,
 * return nBidders.
 */
int BidderCollection::findBidder (std::string name) const
{
  int found = size;
  for (int i = 0; i < size && found == size; ++i)
    {
      if (name == elements[i].getName())
	found = i;
    }
  return found;
}


// Print the collection
void BidderCollection::print (std::ostream& out) const
{
  out << size << "/" << MaxSize << "{";
  for (int i = 0; i < size; ++i)
    {
      out << "  ";
      elements[i].print (out);
      out << "\n";
    }
  out << "}";
}



  // Comparison operators
bool BidderCollection::operator== (const BidderCollection& bc) const
{
  if (size == bc.size)
    {
      for (int i = 0; i < size; ++i)
	if (!(elements[i] == bc.elements[i]))
	  return false;
      return true;
    }
  else
    return false;
}


bool BidderCollection::operator< (const BidderCollection& bc) const
{
  if (size == bc.size)
    {
      for (int i = 0; i < size; ++i)
	{
	  if (elements[i] < bc.elements[i])
	    return true;
	  else if (bc.elements[i] < elements[i])
	    return false;
	  // else keep going
	}

      // All of the elements are equal
      return false;
    }
  else
    return size < bc.size;
}
itemcollection.h
#ifndef ITEMCOLLECTION_H
#define ITEMCOLLECTION_H

#include "items.h"
#include <iostream>

class ItemCollection {

  int MaxSize;
  int size;
  Item* elements; // array of items 

public:

  /**
   * Create a collection capable of holding the indicated number of items
   */
  ItemCollection (int MaxItems = 1000);

  // Big 3
  ItemCollection (const ItemCollection&);
  ItemCollection& operator= (const ItemCollection&);
  ~ItemCollection ();


  

  // Access to attributes
  int getMaxSize() const {return MaxSize;}

  int getSize() const {return size;}



  // Access to individual elements

  const Item& get(int index) const {return elements[index];}



  // Collection operations


  void addInTimeOrder (const Item& value);
  //  Adds this item into a position such that 
  //   all items are ordered by the time at which the auction for the
  //   item ends.
  //Pre: getSize() < getMaxSize()



  void remove (int index);
  // Remove the item at the indicated position
  //Pre: 0 <= index < getSize()


  /**
   * Read all items from the indicated file
   */
  void readItems (std::string fileName);

  // Print the collection
  void print (std::ostream& out) const;


  // Comparison operators
  bool operator== (const ItemCollection&) const;
  bool operator< (const ItemCollection&) const;

};


inline
std::ostream& operator<< (std::ostream& out, const ItemCollection& b)
{
  b.print(out);
  return out;
}

#endif


itemcollection.cpp
#include <iostream>
#include "arrayUtils.h"
#include <fstream>

#include "itemcollection.h"

//
//  Items up for auction
//

using namespace std;


/**
 * Create a collection capable of holding the indicated number of items
 */
ItemCollection::ItemCollection (int MaxItems)
  : MaxSize(MaxItems), size(0)
{
  elements = new Item [MaxSize];
}


// Big 3

ItemCollection::ItemCollection (const ItemCollection& bc)
  : MaxSize(bc.MaxSize), size(bc.size)
{
  elements = new Item [MaxSize];
  for (int i = 0; i < size; ++i)
    elements[i] = bc.elements[i];
}


ItemCollection& ItemCollection::operator= (const ItemCollection& bc)
{
  if (this != &bc)
    {
      delete [] elements;
      size = bc.size;
      MaxSize = bc.MaxSize;
      elements = new Item [MaxSize];
      for (int i = 0; i < size; ++i)
	elements[i] = bc.elements[i];
    }
  return *this;
}

ItemCollection::~ItemCollection ()
{
  delete [] elements;
}





// Collection operations


void ItemCollection::addInTimeOrder (const Item& value)
//  Adds this item into a position such that 
//   all items are ordered by the time the item' auction ends
//Pre: getSize() < getMaxSize()
{
  addInOrder (elements, size, value);
}


void ItemCollection::remove (int index)
// Remove the item at the indicated position
//Pre: 0 <= index < getSize()
{
  removeElement (elements, size, index);
}



/**
 * Read all items from the indicated file
 */
void ItemCollection::readItems (std::string fileName)
{
  size = 0;
  ifstream in (fileName.c_str());
  int nItems;
  in >> nItems;
  for (int i = 0; i < nItems; ++i)
    {
      Item item;
      item.read (in);
      addInTimeOrder (item);
    }
}

// Print the collection
void ItemCollection::print (std::ostream& out) const
{
  out << size << "/" << MaxSize << "{";
  for (int i = 0; i < size; ++i)
    {
      out << "  ";
      elements[i].print (out);
      out << "\n";
    }
  out << "}";
}



  // Comparison operators
bool ItemCollection::operator== (const ItemCollection& ic) const
{
  if (size == ic.size)
    {
      for (int i = 0; i < size; ++i)
	if (!(elements[i] == ic.elements[i]))
	  return false;
      return true;
    }
  else
    return false;
}


bool ItemCollection::operator< (const ItemCollection& ic) const
{
  if (size == ic.size)
    {
      for (int i = 0; i < size; ++i)
	{
	  if (elements[i] < ic.elements[i])
	    return true;
	  else if (ic.elements[i] < elements[i])
	    return false;
	  // else keep going
	}

      // All of the elements are equal
      return false;
    }
  else
    return size < ic.size;
}