Default and Copy Constructors

Steven J. Zeil

Last modified: Jul 04, 2014

Contents:
1. The Default Constructor
2. Copy Constructors
2.1 Where Do We Use a Copy Constructor?
2.2 Compiler-Generated Copy Constructors
2.3 Example: Bid: Compiler-Generated Copy Constructor
2.4 Example: BidCollection: Compiler-Generated Copy Constructor
2.5 Writing a BidCollection Copy Constructor
2.6 Shallow & Deep Copying
3. Assignment
3.1 Compiler-Generated Assignment Ops

1. The Default Constructor

The default constructor is a constructor that takes no arguments. This is the constructor you are calling when you declare an object with no parameters. E.g.,

std::string s;

Declaring a Default Constructor

It might be declared like this

class Time {
public:
   Time();
    ⋮

or with defaults:

namespace std {
class string {
public:
    ⋮
   string(char* s = "");
   ⋮

Either way, we can call it with no parameters.


Why ‘default’?


Implicit Use: Arrays

For example, if we declared:

std::string words[5000];

then each of the 5000 elements of this array will be initialized using the default constructor for string


Implicit Use: Other Constructors

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

   Bid();
};

 
Bid::Bid() 
{
   amount = 0.0;
}


Explicit Construction in Other Constructors

If we don’t want the default value, we need to explicitly perform some other initialization:

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

   Bid();
};

 

Bid::Bid() 
{
   amount = 0.0;
   bidPlacedAt = Time(8, 0, 0); // 8 AM
}

This actually constructs a Time object and then copies it.


Explicit Construction: Initializer Lists

Alternate way to explicitly perform some other initialization:



Bid::Bid() 
  : bidPlacedAt(8, 0, 0) // 8 AM
{
   amount = 0.0;
}



Initializer Lists


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

   Bid();
};

 
Bid::Bid() 
  : bidderName(""), amount(0.0),
    itemName("knick-knack"), bidPlacedAt(8, 0, 0)
{
}



The Helpful Compiler

If we create no constructors at all for a class, the compiler generates a default constructor for us.


Example: Name

class Name {
public:
   string getGivenName();
   void setGivenName (string);

   string getSurName();
   void setSurName (string);
private:
   string givenName;
   string surName;
};


Example: Name 2

class Name {
public:
   Name (string gName, string sName)
     : givenName(gName), surName(sName) {}

   string getGivenName();
   void setGivenName (string);

   string getSurName();
   void setSurName (string);
private:
   string givenName;
   string surName;
};


Example: Name 3

name3.h


Example: BidCollectionBook implicit default constructor


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

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


Example: BidCollection explicit default constructor


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

  BidCollection (int MaxBids = 1000);

  /**
   * Read all bids from the indicated file
   */
  void readBids (std::string fileName);
};
   ⋮
BidCollection::BidCollection (int theMaxSize)
{
  MaxSize = theMaxSize;
  size = 0;
  elements = new Bid[MaxSize];
}

2. Copy Constructors


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?


Where Do We Use a Copy Constructor?

The copy constructor gets used in 5 situations:

  1. When you declare a new object as a copy of an old one:

    Time time2 (time1);
    
         or 
    
    Time time2 = time1;
    
  2. When a function call passes a parameter “by copy” (i.e., the formal parameter does not have a &):

    void foo (Time b, int k);
      ⋮
    
    Time noon (12, 0, 0);
    foo (noon, 0);   // foo actually gets a copy of noon
    
    
  3. When a function returns an object:

    
    Time foo (int k);
    {
      Time t (k, 0, 0);
      ⋮
      return t;
    }
    
    
  4. When explicitly invoked in another constructor’s initialization list:

       Name (string gName, string sName)
     : givenName(gName), surName(sName) {}
    
    
    
  5. When an object is a data member of another class for which the compiler has generated its own copy constructor.

2.2 Compiler-Generated Copy Constructors


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


Example: Bid: Compiler-Generated Copy Constructor

struct Bid {
  std::string bidderName;
  double 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;
  double 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

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

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

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

bcafterNoonBids

removeLate removes the remaining morning bid from bc.

BidCollection removeLate (BidCollection bc, Time t)
{
    ⋮
  return bc;
}
    ⋮
BidCollection afterNoonBids =
   removeLate (bids, Time(12,0,0));


Trouble: bids is corrupted

bids

That’s not the worst of it!

When we exit removeLate,

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

the destructor for BidCollection is called on bc

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

What a Mess!

Both collections have “dangling pointers”


Avoiding this Problem

We could

2.5 Writing a BidCollection Copy Constructor


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!

removeLatebids

bidCollApplication.cpp

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

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

bcafterNoonBids

removeLate removes the remaining morning bid from bc.

BidCollection removeLate (BidCollection bc, Time t)
{
    ⋮
  return bc;
}
    ⋮
BidCollection afterNoonBids =
   removeLate (bids, Time(12,0,0));


Much Nicer

BidCollectionbc

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 … ) then the compiler-provided copy constructor amounts to a bit-by-bit copy.


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


Assignment

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

3.1 Compiler-Generated Assignment Ops


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. To see why, suppose we had some application code:

bidCollAsst.cpp

bids
bidsbc

bidCollAsst.cpp

bc
bc

Again: bids is corrupted

bids

Avoiding this Problem

We could