Constructors and the Rule of the Big 3

Steven J Zeil

Last modified: Jan 24, 2018
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;
Book b;


Declaration

A default constructor might be declared to take no parameters at all, like this:

class Book {
public:
   Book();
    ⋮

or with defaults on all of its parameters:

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

Either way, we can call it with no parameters.


Why Default?

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`

Compiler-Generated Default Constructors

Because arrays are so common, it is rare that we would actually want a class with no default constructor. The C++ compiler tries to be helpful:

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

1.1 Default constructor examples


Book default constructor

If we haven’t created a default constructor for Books, we could add one easily enough:

bookDefault.h
class Book {
public:
  typedef AuthorIterator AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors
  Book();

  std::string getTitle() const        { return title; }
  void setTitle(std::string theTitle) {  title = theTitle; }
      ⋮
private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  AuthorNode* first;
  AuthorNode* last;

  friend class AuthorIterator;  
};

To implement it, we need to come up with something reasonable for each data member:

Book::Book ()
   numAuthors(0), first(0), last(0)
{
}

(We’ll let the strings title and isbn default to the std::string default of "", and we trust that the Publisher class provides a reasonable default constructor of its own.)


Address default constructor

addDefault.h
class Address {
public:
  Address (std::string theStreet, std::string theCity,
           std::string theState, std::string theZip);

  std::string getStreet() const;
  void putStreet (std::string theStreet);
  
  std::string getCity() const;
  void putCity (std::string theCity);
  
  std::string getState() const;
  void putState (std::string theState);
  
  std::string getZip() const;
  void putZip (std::string theZip);
  
private:
  std::string street;
  std::string city;
  std::string state;
  std::string zip;
};

This class has no explicit default constructor. It does have another constructor, however, so the compiler will not generate an automatic default constructor for us.

There’s no good reason why we should not have a default constructor for this class. We might want arrays of addresses sometime.

addrDefault.h
class Address {
public:
  Address (std::string theStreet, std::string theCity,
           std::string theState, std::string theZip);

  Address () {}

As you can see, there’s not much to it. All the data members are strings, and there’s really nothing much better for us to do than just rely on the default constructors for strings.

2 Copy Constructors

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

Foo (const Foo& oldCopy);

Where are Copy Constructors Used?

The copy constructor gets used in 5 situations:

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

Book book2 (book1);

or

Book book2 = book1;

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

void foo (Book b, int k);
  ⋮

Book text361 (0201308787, budd, 
     "Data Structures in C++ Using the Standard Template Library",
     1998, 1);
foo (text361, 0);   // foo actually gets a copy of text361

3) When a function returns an object:

Book foo (int k);
{
  Book b;
  ⋮
  return b; // a copy of b is placed in the caller's memory area
}

4) When data members are initialized in a constructor’s initialization list:

copyInInit.cpp
Author::Author (std::string theName, 
                Address theAddress, long id)
  : name(theName), 
    address(theAddress),
    identifier(id)
{
}

5) When an object is a data member of another class for which the compiler has generated its own copy constructor.


Compiler-Generated Copy Constructors

As you can see from that list, the copy constructor gets used a lot. It would be very awkward to work with a class that did not provide a copy constructor.

So, again, the compiler tries to be helpful.

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

2.1 Copy Constructor Examples

2.1.1 Address

In the case of our Address class, we have not provided a copy constructor, so the compiler would generate one for us.

The implicitly generated copy constructor would behave as if it had been written this way:

Address::Address (const Address& a)
  : street(a.street), city(a.city), 
    state(a.state), zip(a.zip)
{
}

2.1.2 Book - simple arrays

book1.h
#ifndef BOOK_H
#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const;
  void setTitle(std::string theTitle);

  int getNumberOfAuthors() const;

  std::string getISBN() const;
  void setISBN(std::string id);

  Publisher getPublisher() const;
  void setPublisher(const Publisher& publ);

  AuthorPosition begin() const;
  AuthorPosition end() const;

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  static const int MAXAUTHORS = 12;
  Author authors[MAXAUTHORS];

};

#endif

Consider the problem of copying a book, and for the moment we will work with our “simple” arrays version.


Book - simple arrays (cont.)

 
If we start with a single book object, b1, as shown here, and then we execute

Book b2 = b1;
   because we have provided no copy constructor, the compiler-generated
  version is used and each data member is copied.

Compiler-Generated Copy

 
The new result would be something like this, which looks just fine.

2.1.3 Book - dynamic arrays

Now let’s consider the problem of copying a book implemented using dynamically allocated arrays.

book2a.h
#ifndef BOOK_H
#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const        { return title; }

  void setTitle(std::string theTitle) {  title = theTitle; }

  int getNumberOfAuthors() const { return numAuthors; }

  std::string getISBN() const  { return isbn; }
  void setISBN(std::string id) { isbn = id; }

  Publisher getPublisher() const { return publisher; }
  void setPublisher(const Publisher& publ) { publisher = publ; }


  AuthorPosition begin() const;
  AuthorPosition end() const;

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  int MAXAUTHORS;
  Author* authors;

};

#endif

Copying dynamic arrays

 
If we start with a single book object, b1, as shown here, and then we execute

Book b2 = b1;

because we have provided no copy constructor, the compiler-generated version is used and each data member is copied.


Compiler-Generated Copy

 
The new result would be something like this.


Co-authors Should Not Share

 
To understand why this is so bad, suppose that we later did

b1.removeAuthor(b1.begin());
   to remove the first author from book `b1`.

Unplanned Sharing Leads to Corruption

addAuthors.cpp
void Book::addAuthor (Book::AuthorPosition at, const Author& author)
{
  if (numAuthors >= MAXAUTHORS)
    {
      Author* newAuthors = new Author[2*MAXAUTHORS];
      for (int i = 0; i < MAXAUTHORS; ++i)
	newAuthors[i] = authors[i];
      MAXAUTHORS *= 2;
      delete [] authors;
      authors = newAuthors;
    }
  int i = numAuthors;
  int atk = at - authors;
  while (i > atk) 
    {
      authors[i] = authors[i-1];
      i--;
    }
  authors[atk] = author;
  ++numAuthors;
}

 
That’s not even the worst possible scenario. Recall that our code for adding authors works by

So if we start from this data state, and then add 3 authors to book 1 (“Doe”, “Smith”, and “Jones”), …


Corrupted Data

 
… we would end up with the state shown here.


Sometime it’s Better to Have 2 Copies

 
What we really wanted, after the copy:

Book b2 = b1;

is something more like this:

But to get that, we will not be able to rely on the automatically generated copy constructor for Books.

2.2 Shallow vs Deep Copy

Copy operations are distinguished by how they treat pointers:


This was a Shallow Copy

 


This was a Deep Copy

 


Book - linked list

book3a.h
#ifndef BOOK_H
#include "author.h"
#include "authoriterator.h"
#include "publisher.h"


class Book {
public:
  typedef AuthorIterator AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const        { return title; }

  void setTitle(std::string theTitle) {  title = theTitle; }

  int getNumberOfAuthors() const { return numAuthors; }

  std::string getISBN() const  { return isbn; }
  void setISBN(std::string id) { isbn = id; }

  Publisher getPublisher() const { return publisher; }
  void setPublisher(const Publisher& publ) { publisher = publ; }

  AuthorPosition begin() const;
  AuthorPosition end() const;

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  AuthorNode* first;
  AuthorNode* last;

  friend class AuthorIterator;  
};

#endif

 
Now let’s consider the problem of copying a book implemented using linked lists.

If we start with a single book object, b1, as shown here, and then we execute

Book b2 = b1;

because we have provided no copy constructor, the compiler-generated version is used and each data member is copied.


Shallow Copy of Linked List

 
The new result would be something like this.


Corrupted List

For example, suppose that we later did

b1.removeAuthor(b1.begin());
   to remove the first author from book `b1`.

 
Afterwards, we would have something like this. The change to b1 has corrupted b2.


List-Based Copy Constructor

If we want to implement a proper deep copy for our books, we start by adding the constructor declaration:

bookLLCopyCons.h
class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors

  Book (const Book& b);

  std::string getTitle() const        { return title; }
  void setTitle(std::string theTitle) {  title = theTitle; }
     ⋮
private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  AuthorNode* first;
  AuthorNode* last;

  friend class AuthorIterator;  
};

Then we supply a function body for this constructor.

Book::Book (const Book& b)
  : title(b.title), isbn(b.isbn), 
    publisher(b.publisher),
    numAuthors(0), first(0), last(0)
{
  for (AuthorPosition p = b.begin(); p != b.end();
        ++p)
    addAuthor(end(), *p);
}

Linked-List Deep Copy

 
The end result after the copy should be this.


Book - std::list

book4.h
#ifndef BOOK_H

#include <list>

#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef std::list<Author>::iterator AuthorPosition;
  typedef std::list<Author>::const_iterator const_AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const        { return title; }

  void setTitle(std::string theTitle) {  title = theTitle; }

  int getNumberOfAuthors() const { return numAuthors; }

  std::string getISBN() const  { return isbn; }
  void setISBN(std::string id) { isbn = id; }

  Publisher getPublisher() const { return publisher; }
  void setPublisher(const Publisher& publ) { publisher = publ; }

  const_AuthorPosition begin() const;
  const_AuthorPosition end() const;

  AuthorPosition begin();
  AuthorPosition end();

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  std::list<Author> authors;
};

#endif

Now let’s consider the problem of copying a book implemented using std::list.

 
If we start with a single book object, b1, as shown here, and then we execute

Book b2 = b1;

because we have provided no copy constructor, the compiler-generated version is used and each data member is copied.


Copy of std::list

 
The new result would be something like this. This looks just fine.

We don’t need to override the compiler-generated copy constructor in this case.

2.3 Trusting the Compiler-Generated Copy Constructor

By now, you may have perceived a pattern.

Shallow copy is wrong when…

And it follows that:

Compiler-generated copy constructors are wrong when…

3 Assignment

When we write book1 = book2, that’s shorthand for book1.operator=(book2).

We tend to do a lot of assignment in typical programming, so, once more, the compiler tries to be helpful:

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


A Compiler-Generated Assignment Op

addressDecl.h
class Address {
public:
  Address (std::string theStreet, std::string theCity,
           std::string theState, std::string theZip);

  std::string getStreet() const;
  void putStreet (std::string theStreet);
  
  std::string getCity() const;
  void putCity (std::string theCity);
  
  std::string getState() const;
  void putState (std::string theState);
  
  std::string getZip() const;
  void putZip (std::string theZip);
  
private:
  std::string street;
  std::string city;
  std::string state;
  std::string zip;
};

For example, we have not provided an assignment operator for Address class. Therefore the compiler will attempt to generate one, just as if we had written

addrAsst.h
class Address {
public:
  Address (std::string theStreet, std::string theCity,
           std::string theState, std::string theZip);


  Address& operator= (const Address&);

A Compiler-Generated Assignment Op (cont.)

The automatically generated body for this assignment operator will be the equivalent of

Address& Address::operator= (const Address& a)
{
  street = a.street;
  city = a.city;
  state = a.state;
  zip = a.zip;
  return *this;
}

And that automatically generated assignment is just fine for Address.


Return values in Asst Ops

addrAsst.cpp
Address& Address::operator= (const Address& a)
{
  street = a.street;
  city = a.city;
  state = a.state;
  zip = a.zip;
  return *this;
}

The return value returns the value just assigned, allowing programmers to chain assignments together:

addr3 = addr2 = addt1;

This can simplify code where a computed value needs to be tested and then maybe used again if it passes the test, e.g.,

while ((x = foo(y)) > 0) {
  do_something_useful_with(x);
}

3.1 Assignment Examples

3.1.1 Book - Simple Arrays

book1.h
#ifndef BOOK_H
#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const;
  void setTitle(std::string theTitle);

  int getNumberOfAuthors() const;

  std::string getISBN() const;
  void setISBN(std::string id);

  Publisher getPublisher() const;
  void setPublisher(const Publisher& publ);

  AuthorPosition begin() const;
  AuthorPosition end() const;

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  static const int MAXAUTHORS = 12;
  Author authors[MAXAUTHORS];

};

#endif

Consider the problem of copying a book, and for the moment we will work with our “simple” arrays version.


Book - Simple Arrays (cont.)

 
If we start with a single book object, b1, as shown here, and then we execute

b2 = b1;

because we have provided no assignment operator, the compiler-generated version is used and each data member is copied.


Compiler-Generated Assignment

 
The new result would be something like this, which looks just fine.

So in this case, we can rely on the compiler-generated assignment operator.

3.1.2 Book - dynamic arrays

Now let’s consider the problem of copying a book implemented using dynamically allocated arrays.

book2a.h
#ifndef BOOK_H
#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const        { return title; }

  void setTitle(std::string theTitle) {  title = theTitle; }

  int getNumberOfAuthors() const { return numAuthors; }

  std::string getISBN() const  { return isbn; }
  void setISBN(std::string id) { isbn = id; }

  Publisher getPublisher() const { return publisher; }
  void setPublisher(const Publisher& publ) { publisher = publ; }


  AuthorPosition begin() const;
  AuthorPosition end() const;

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  int MAXAUTHORS;
  Author* authors;

};

#endif

Assigning with dynamic arrays

 
If we start with a single book object, b1, as shown here, and then we execute

b2 = b1;

because we have provided no assignment op, the compiler-generated version is used and each data member is copied.


Compiler-Generated Asst

 
The new result would be something like this.


This Seems Familiar…


Implementing Deep-Copy Assignment

If we want to implement a proper deep copy for our books, we start by adding the operator declaration:

bookAsst.h
class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors

  Book (const Book& b);
  const Book& operator= (const Book& b);

  std::string getTitle() const        { return title; }
  void setTitle(std::string theTitle) {  title = theTitle; }
     ⋮
private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  int MAXAUTHORS;
  Author* authors;

};

Then we supply a function body for this operator.

bookAsst.cpp
const Book& Book::operator= (const Book& b)
{
  title = b.title;
  isbn = b.isbn;
  publisher = b.publisher;
  numAuthors = b.numAuthors;
  if (b.numAuthors > MAXAUTHORS)
    {
      MAXAUTHORS = b.MAXAUTHORS;
      delete [] authors;
      authors = new Author[MAXAUTHORS];
    }
  for (int i = 0; i < numAuthors; ++i)
    authors[i] = b.authors[i];
  return *this;
}

3.1.3 Book - linked list

 
Now let’s consider the problem of copying a book implemented using linked lists.

If we start with a single book object, b1, as shown here, and then we execute

b2 = b1;

because we have provided no assignment op, the compiler-generated version is used and each data member is copied.


Shallow Copy of Linked List

 
The new result would be something like this.


Shallow Copy of Linked List

 


Assignment via Linked Lists

If we want to implement a proper deep copy for our books, we start by adding the operator declaration:

bookLLAsst.h
class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors

  Book (const Book& b);
  const Book& operator= (const Book& b);

  std::string getTitle() const        { return title; }
  void setTitle(std::string theTitle) {  title = theTitle; }
     ⋮
private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  AuthorNode* first;
  AuthorNode* last;

  friend class AuthorIterator;  
};

Then we supply a function body for this operator.

bookLLAsst.cpp
const Book& Book::operator= (const Book& b)
{
  title = b.title;
  isbn = b.isbn;
  publisher = b.publisher;
  numAuthors = 0;
  for (AuthorPosition p = begin(); p != end();)
    {
      AuthorPosition nxt = p;
      ++nxt;
      delete p;
      p = nxt;
    }
  first =  last = 0;
  for (AuthorPosition p = b.begin(); p != b.end(); ++p)
    addAuthor(end(), *p);
  return *this;
}

Assignment Result

 
The end result after the assignment should be this:


Self-Assignment

If we assign something to itself:

x = x;

we normally expect that nothing really happens.

But when we are writing our own assignment operators, that’s not always the case. Sometimes assignment of an object to itself is a nasty special case that breaks thing badly.

bookLLAsst.cpp
const Book& Book::operator= (const Book& b)
{
  title = b.title;
  isbn = b.isbn;
  publisher = b.publisher;
  numAuthors = 0;
  for (AuthorPosition p = begin(); p != end();)
    {
      AuthorPosition nxt = p;
      ++nxt;
      delete p;
      p = nxt;
    }
  first =  last = 0;
  for (AuthorPosition p = b.begin(); p != b.end(); ++p)
    addAuthor(end(), *p);
  return *this;
}

What happens if we do b1 = b1;?


Self-Assignment Can Corrupt

So, instead of b1 = b1; leaving b1 unchanged, it would actually destroy b1.


Checking for Self-Assignment

bookSelfAsst.cpp
const Book& Book::operator= (const Book& b)
{
  if (this != &b)
    {
      title = b.title;
      isbn = b.isbn;
      publisher = b.publisher;
      numAuthors = 0;
      for (AuthorPosition p = begin(); p != end();)
        {
          AuthorPosition nxt = p;
          ++nxt;
          delete p;
          p = nxt;
        }
      first =  last = 0;
      for (AuthorPosition p = b.begin(); p != b.end(); ++p)
         addAuthor(end(), *p);
    }
  return *this;
}

This is safer.

3.1.4 Book - std::list

book4.h
#ifndef BOOK_H

#include <list>

#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef std::list<Author>::iterator AuthorPosition;
  typedef std::list<Author>::const_iterator const_AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const        { return title; }

  void setTitle(std::string theTitle) {  title = theTitle; }

  int getNumberOfAuthors() const { return numAuthors; }

  std::string getISBN() const  { return isbn; }
  void setISBN(std::string id) { isbn = id; }

  Publisher getPublisher() const { return publisher; }
  void setPublisher(const Publisher& publ) { publisher = publ; }

  const_AuthorPosition begin() const;
  const_AuthorPosition end() const;

  AuthorPosition begin();
  AuthorPosition end();

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  std::list<Author> authors;
};

#endif

Now let’s consider the problem of assigning books implemented using std::list.

 
If we start with a single book object, b1, as shown here, and then we execute

b2 = b1;

because we have provided no asst op, the compiler-generated version is used and each data member is copied.


Assigned std::list

 
The new result would be something like this. This looks just fine.

We don’t need to override the compiler-generated assignment operator in this case.

3.2 Trusting the Compiler-Generated Assignment Operator

By now, you may have perceived a pattern.

Shallow copy is wrong when…

And it follows that:

Compiler-generated assignment operators are wrong when…

4 Destructors

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

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


A Compiler-Generated Destructor

addrNoDestructor.h
class Address {
public:
  Address (std::string theStreet, std::string theCity,
           std::string theState, std::string theZip);

  std::string getStreet() const;
  void putStreet (std::string theStreet);
  
  std::string getCity() const;
  void putCity (std::string theCity);
  
  std::string getState() const;
  void putState (std::string theState);
  
  std::string getZip() const;
  void putZip (std::string theZip);
  
private:
  std::string street;
  std::string city;
  std::string state;
  std::string zip;
};


class Author
{
public:
  Author (std::string theName, Address theAddress, long id);

  std::string getName() const        {return name;}
  void putName (std::string theName) {name = theName;}

  const Address& getAddress() const   {return address;}
  void putAddress (const Address& addr) {address = addr;}

  long getIdentifier() const     {return identifier;}

private:
  std::string name;
  Address address;
  const long identifier;
};

We have not declared or implemented a destructor for any of our classes. For Address and Author, that’s OK.

4.1 Destructor Examples

4.1.1 Book - simple arrays

book1.h
#ifndef BOOK_H
#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const;
  void setTitle(std::string theTitle);

  int getNumberOfAuthors() const;

  std::string getISBN() const;
  void setISBN(std::string id);

  Publisher getPublisher() const;
  void setPublisher(const Publisher& publ);

  AuthorPosition begin() const;
  AuthorPosition end() const;

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  static const int MAXAUTHORS = 12;
  Author authors[MAXAUTHORS];

};

#endif

This version of the book has all of its data in a single block of memory. Assuming that each data member knows how to clean up its own internal storage, there’s really nothing we would have to do when this book gets destroyed.

We can rely on the compiler-provided destructor.

4.1.2 Book - dynamic arrays

book2.h
#ifndef BOOK_H
#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors

  Book (const Book& b);
  ~Book();
  const Book& operator= (const Book& b);


  std::string getTitle() const        { return title; }

  void setTitle(std::string theTitle) {  title = theTitle; }

  int getNumberOfAuthors() const { return numAuthors; }

  std::string getISBN() const  { return isbn; }
  void setISBN(std::string id) { isbn = id; }

  Publisher getPublisher() const { return publisher; }
  void setPublisher(const Publisher& publ) { publisher = publ; }

  AuthorPosition begin() const;
  AuthorPosition end() const;

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  int MAXAUTHORS;
  Author* authors;

};

#endif

 
In this version of the Book class, a portion of the data is kept on the heap, in a number of linked list nodes allocated on the heap.


Implementing the Dyn. ArrayDestructor

We start by adding the destructor declaration:

bookDestr.h
class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors

  Book (const Book& b);
  const Book& operator= (const Book& b);
  ~Book();

  std::string getTitle() const        { return title; }
  void setTitle(std::string theTitle) {  title = theTitle; }
     ⋮
private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  int MAXAUTHORS;
  Author* authors;

};

Then we supply a function body for this destructor.

Book::~Book()
{
  delete [] authors;
}

Not much needs to be done - just delete the pointer to the array of authors.

4.1.3 Book - linked list

In this version of the Book class, a portion of the data is kept in an array allocated on the heap.

book3.h
#ifndef BOOK_H
#include "author.h"
#include "authoriterator.h"
#include "publisher.h"


class Book {
public:
  typedef AuthorIterator AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors

  Book (const Book& b);
  ~Book();
  const Book& operator= (const Book& b);

  std::string getTitle() const        { return title; }

  void setTitle(std::string theTitle) {  title = theTitle; }

  int getNumberOfAuthors() const { return numAuthors; }

  std::string getISBN() const  { return isbn; }
  void setISBN(std::string id) { isbn = id; }

  Publisher getPublisher() const { return publisher; }
  void setPublisher(const Publisher& publ) { publisher = publ; }

  AuthorPosition begin() const;
  AuthorPosition end() const;

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  AuthorNode* first;
  AuthorNode* last;

  friend class AuthorIterator;  
};

#endif

 


Implementing a Linked List Destructor

We start by adding the destructor declaration:

bookLLDestr.h
class Book {
public:
  typedef const Author* AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors

  Book (const Book& b);
  const Book& operator= (const Book& b);
  ~Book();

  std::string getTitle() const        { return title; }
  void setTitle(std::string theTitle) {  title = theTitle; }
     ⋮
private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  AuthorNode* first;
  AuthorNode* last;

  friend class AuthorIterator;  
};

Then we supply a function body for this destructor.

Book::~Book()
{
  AuthorPosition nxt;
  for (AuthorPosition current = begin(); current != end(); current = nxt)
    {
      nxt = current;
      ++nxt;
      delete current.pos;
    }
}

This one is more elaborate. We need to walk the entire list, deleting each node as we come to it.

4.1.4 Book - std::list

book4.h
#ifndef BOOK_H

#include <list>

#include "author.h"
#include "publisher.h"


class Book {
public:
  typedef std::list<Author>::iterator AuthorPosition;
  typedef std::list<Author>::const_iterator const_AuthorPosition;

  Book (Author);                       // for books with single authors
  Book (const Author[], int nAuthors); // for books with multiple authors


  std::string getTitle() const        { return title; }

  void setTitle(std::string theTitle) {  title = theTitle; }

  int getNumberOfAuthors() const { return numAuthors; }

  std::string getISBN() const  { return isbn; }
  void setISBN(std::string id) { isbn = id; }

  Publisher getPublisher() const { return publisher; }
  void setPublisher(const Publisher& publ) { publisher = publ; }

  const_AuthorPosition begin() const;
  const_AuthorPosition end() const;

  AuthorPosition begin();
  AuthorPosition end();

  void addAuthor (AuthorPosition at, const Author& author);
  void removeAuthor (AuthorPosition at);

private:

  std::string title;
  int numAuthors;
  std::string isbn;
  Publisher publisher;

  std::list<Author> authors;
};

#endif

We can rely on the compiler-provided destructor.

4.2 Trusting the Compiler-Generated Destructor

By now, you may have perceived a pattern.

Compiler-generated destructors are wrong when…

5 The Rule of the Big 3

The Big 3

The “Big 3” are the

By now, you may have noticed a pattern with these.


The Rule of the Big 3

The rule of the big 3 states that,

if you provide your own version of any one of the big 3, you should provide your own version of all 3.

Why? Because we don’t trust the compiler-generated …

So if we don’t trust one, we don’t trust any of them.