The big 3 in C++ class design are
the copy constructor,
the assignment operator, and
the destructor.
For each of these, if you do not provide them, the compiler generates a version for you.
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.
So your choices as a class designer come down to:
The compiler-generated version is acceptable for all three.
You have provided your own version of all three.
You have provided private versions of the copy constructor and assignment operator so no one can use them.
Like the default constructor, copy constructors are ordinary constructors that just happens to get used in some special places.
The copy constructor for a class Foo
is the constructor of the
form:
Foo (const Foo& oldCopy);
The copy constructor gets used in 5 situations:
When you declare a new object as a copy of an old one:
Day day2 (day1);
or
Day day2 = day1;
When a function call passes a parameter “by copy” (i.e., the formal parameter does not have a &):
void foo (Day b, int k); <[:]> Day indepDay (1776, 7, 4); foo (indepDay, 0); // foo actually gets a copy of indepDay
When a function returns an object:
Day foo (int k); { Day d; <[:]> return d; }
When data members are initialized in a constructor's initialization list:
Contact::Contact (std::string theName, Address theAddress, long id) : <[+]>name(theName)<[-]>, <[+]> address(theAddress),<[-]> identifier(id) { }
When an object is a data member of another class for which the compiler has generated its own copy constructor.
If we do not create a copy constructor for a class, the compiler generates one for us.
copies each data member via their individual copy constructors.
In the case of our Contact
class, the implicitly generated
copy constructor would behave as if it had been written this way.
Contact::Contact (const Contact& a) : theName(a.theName), theAddress(a.theAddress) {}
This is, in fact, a perfectly good copy function for this class, so we might as well use the compiler-generated version.
If our data members do not have explicit copy constructors (and their data members do not have explicit copy constructors, and …)
<br>
</br>
then the compiler-provided copy constructor amounts to a bit-by-bit copy.
Suppose that MailingList provides no explicit copy constructor:
MailingList x = y;
This is not good. Note that adding & removing contacts from one mailinglist will affect the other.
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.
MailingList::MailingList(const MailingList& ml)
: first(NULL), last(NULL), theSize(0)
{
for (ML_Node* current = ml.first; current != NULL;
current = current->next)
addContact(current->contact);
}
Your ADT has pointers among its data members, and
You don't want to share the objects being pointed to.
When we write
mlist1 = mlist2
, that's shorthand for either
mlist1.operator=(mlist2)
or
operator=(mlist1,mlist2)
, depending on whether
operator=
has been declared as a member function of the
MailingList
class or as an ordinary, stand-alone function.
If you don't provide your own assignment operator for a class, the compiler generates one automatically.
assigns each data member in turn.
If none of the members have programmer-supplied assignment ops, then this is a shallow copy
For example, we have not provided an assignment operator for
the
Contact
class. Therefore the compiler will attempt to
generate one, just as if we had written
class Contact { public: <[:]> <[+]> Contact& operator= (const Contact&);<[-]> <[:]>
The automatically generated body for this assignment operator will be the equivalent of
Contact& Contact::operator= (const Contact& a) { theName = a.theName; theAddress = a.theAddress; return *this; }
And that automatically generated assignment is just fine for
Contact
.
Contact& Contact::operator= (const Contact& a) { theName = a.theName; theAddress = a.theAddress; return *this; }
The return value returns the value just assigned, allowing programmers to chain assignments together:
con3 = con2 = con1;
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);
}
Assume that MailingList does not provide an explicit assignment op:
x = y;
class MailingList
{
public:
MailingList();
MailingList(const MailingList&);
~MailingList();
const MailingList& operator= (const MailingList&);
<[:]>
};
const MailingList& MailingList::operator= (const MailingList& ml)
{
if (this != &ml)
{
clear();
for (ML_Node* current = ml.first; current != NULL;
current = current->next)
addContact(current->contact);
}
return *this;
}
void MailingList::clear()
{
ML_Node* current = first;
while (current != NULL)
{
ML_Node* next = current->next;
delete current;
current = next;
}
first = last = NULL;
theSize = 0;
}
Your ADT has pointers among its data members, and
You don't want to share the objects being pointed to.
The automatically generated destructor simply invokes the destructors for any data member objects.
If none of the members have programmer-supplied destructors, does nothing.
MailingList::~MailingList() { clear(); }
MailingList
should not be left with the
compiler-generated destructor.
Because the MailingList
addContact
functions allocate a linked list node
for each contact
We need to make sure that the memory used for these nodes gets recovered
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.
In the Forum: