Default and Copy Constructors

Steven J. Zeil

Last modified: Dec 26, 2016
Contents:

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;

1.1 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.

1.2 Why ‘default’?

1.3 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

1.4 Implicit Use: Other Constructors

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

   Bid();
};

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

1.5 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.

1.6 Explicit Construction: Initializer Lists

Alternate way to explicitly perform some other initialization:

bidcons.cpp

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

1.7 Initializer Lists

1.8 The Helpful Compiler

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

1.9 Example: Name

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

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

1.10 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;
};

1.11 Example: Name 3

name3.h
class Name {
public:
   Name () {}

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

   string getGivenName();
   void setGivenName (string);

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

1.12 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); };

1.13 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

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:

  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:

    copyInInit.cpp
       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

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;
  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:

bidCompilerCopy.cpp
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:

bidCollCompilerCopy.cpp
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 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));

 
Assume we start with this in bids.

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

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));

 
removeLate removes the first morning bid from bc. %ifnot _slides

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));

 
Then removeLate removes the remaining morning bid from bc.%ifnot _slides

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));

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

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

 
Note that we have corrupted the original collection, 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;
}

 
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


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.


 

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

bidCollApplication.cpp
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));

 

removeLate removes the first morning bid from bc.

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));


 
Then removeLate removes the remaining morning bid from bc.

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));


 

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

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

 
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 … ) 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

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. To see why, 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.noLaterThan(t))
        ++i;
     else
        removeElement (elements, size, i);
    }

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

 
Assume we start with this in bids.

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

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.noLaterThan(t))
        ++i;
     else
        removeElement (elements, size, i);
    }

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

 
We remove the first morning bid from bc.

 
Then remove the remaining morning bid from bc.


Again: bids is corrupted

 
Note that we have corrupted the original collection, bids


Avoiding this Problem

We could