Last modified: Mar 11, 2014
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?
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.
In many cases (e.g., strings), this does something entirely reasonable for our purposes.
Be careful, though: the “default constructors” for the primitive types such as int, double and pointers work by doing nothing at all. The value is left uninitialized.
Book default constructor
If we haven’t created a default constructor for Books, we could add one easily enough:
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
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.
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.
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:
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.
This automatically generated version works by copying each data member via their individual copy constructors.
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)
{
}
#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.)
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
Now let’s consider the problem of copying a book implemented using dynamically allocated arrays.
#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
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 authors pointer is copied bit-by-bit
two book objects now share the same author array
Co-authors Should Not Share
b1.removeAuthor(b1.begin());
to remove the first author from book `b1`.
Unplanned Sharing Leads to Corruption
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;
}
Checking to see if there was room in the array.
So if we start from this data state, and then add 3 authors to book 1 (“Doe”, “Smith”, and “Jones”), …
Corrupted Data
b1 deleted the old array,
but b2 still has the old array address in its data member.
Sometime it’s Better to Have 2 Copies
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.
Copy operations are distinguished by how they treat pointers:
In a shallow copy, all pointers are copied.
Leads to shared data on the heap.
In a deep copy, objects pointed to are copied, then the new pointer set to the address of the copied object.
Copied objects keep exclusive access to the things they point to.
This was a Shallow Copy
This was a Deep Copy
Shallow versus Deep
In this example, deep is preferred.
That’s not always the case.
“Shallow” and “deep” are two extremes of a range of possible copies
Book - dynamic arrays
If we want to implement a proper deep copy for our books, we start by adding the constructor declaration:
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;
int MAXAUTHORS;
Author* authors;
};
Then we supply a function body for this constructor.
Book::Book (const Book& b)
: title(b.title), isbn(b.isbn), publisher(b.publisher),
numAuthors(b.numAuthors), MAXAUTHORS(b.MAXAUTHORS)
{
authors = new Author[numAuthors+1];
for (int i = 0; i < numAuthors; ++i)
authors[i] = b.authors[i];
}
Book - linked list
#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
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
Again, the pointer data members (first and last) are copied using the default copy procedure for pointers, which is to simply copy the bits of the pointer.
That has the result of placing the same node addresses in both book objects. In effect, there is one collection of nodes being shared between both books.
Once again, that’s a really, really, bad idea!
Corrupted List
For example, suppose that we later did
b1.removeAuthor(b1.begin());
to remove the first author from book `b1`.
List-Based Copy Constructor
If we want to implement a proper deep copy for our books, we start by adding the constructor declaration:
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
Book - std::list
#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.
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
We don’t need to override the compiler-generated copy constructor in this case.
By now, you may have perceived a pattern.
Shallow copy is wrong when…
Your ADT has pointers among its data members, and
You don’t want to share the objects being pointed to.
And it follows that:
Compiler-generated copy constructors are wrong when…
Your ADT has pointers among its data members, and
You don’t want to share the objects being pointed to.
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.
The automatically generated assignment operator works by assigning each data member in turn.
If none of the members have programmer-supplied assignment ops, then this is a shallow copy
A Compiler-Generated Assignment Op
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
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
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);
}
#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.)
b2 = b1;
because we have provided no assignment operator, the compiler-generated version is used and each data member is copied.
Compiler-Generated Assignment
So in this case, we can rely on the compiler-generated assignment operator.
Now let’s consider the problem of copying a book implemented using dynamically allocated arrays.
#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
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 authors pointer is copied bit-by-bit
two book objects now share the same author array
This Seems Familiar…
We saw earlier that having two books sharing the same array was a bad idea.
The compiler-generated assignment operator implements a shallow copy, and that is not what we want.
Implementing Deep-Copy Assignment
If we want to implement a proper deep copy for our books, we start by adding the operator declaration:
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.
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;
}
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
Again, the pointer data members (first and last) are assigned using the default assignment procedure for pointers, which is to simply copy the bits of the pointer.
That has the result of placing the same node addresses in both book objects. In effect, there is one collection of nodes being shared between both books.
Shallow Copy of Linked List
Once again, that’s a really, really, bad idea!
And, once again, the original nodes in b2 are now unreachable memory leaks.
Assignment via Linked Lists
If we want to implement a proper deep copy for our books, we start by adding the operator declaration:
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.
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
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.
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
The first loop removes all nodes from the destination list.
The second loop copies all nodes in the source list.
But if the source and destination are the same, then by the time we reach the second loop, there won’t be any nodes left to copy.
So, instead of b1 = b1; leaving b1 unchanged, it would actually destroy b1.
Checking for Self-Assignment
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.
We check to see if the object we are assigning to (this) is at the same address as the one we are assigning from
If the two are the same, we leave them alone.
Only if the two addresses are different do we carry on with the assignment.
#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.
b2 = b1;
because we have provided no asst op, the compiler-generated version is used and each data member is copied.
Assigned std::list
We don’t need to override the compiler-generated assignment operator in this case.
By now, you may have perceived a pattern.
Shallow copy is wrong when…
Your ADT has pointers among its data members, and
You don’t want to share the objects being pointed to.
And it follows that:
Compiler-generated assignment operators are wrong when…
Your ADT has pointers among its data members, and
You don’t want to share the objects being pointed to.
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.
The automatically generated destructor simply invokes the destructors for any data member objects.
If none of the members have programmer-supplied destructors, does nothing.
A Compiler-Generated Destructor
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.
#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.
#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
If this object were destroyed, we would need to be sure that the storage allocated for the array is recovered.
That won’t happen in the compiler-generated destructor, because the default action on pointers is to do nothing.
Implementing the Dyn. ArrayDestructor
We start by adding the destructor declaration:
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.
In this version of the Book class, a portion of the data is kept in an array allocated on the heap.
#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
If this object were destroyed, we would need to be sure that the storage allocated for the nodes is recovered.
That won’t happen in the compiler-generated destructor, because the default action on pointers is to do nothing.
Implementing a Linked List Destructor
We start by adding the destructor declaration:
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.
#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
It’s entirely likely that the std::list has pointers inside it, but we trust its destructor to clean up its own internal data structures.
With that in mind, there’s really nothing we would have to do when this book gets destroyed.
We can rely on the compiler-provided destructor.
By now, you may have perceived a pattern.
Compiler-generated destructors are wrong when…
Your ADT has pointers among its data members, and
You don’t want to share the objects being pointed to.
The Big 3
The “Big 3” are the
copy constructor
assignment operator, and
destructor
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 …
copy constructor if our data members include pointers to data we don’t share
assignment operator if our data members include pointers to data we don’t share
destructor if our data members include pointers to data we don’t share
So if we don’t trust one, we don’t trust any of them.