B
blangela
I have decided (see earlier post) to paste my Word doc here so that it
will be simpler for people to provide feedback (by directly inserting
their comments in the post). I will post it in 3 parts to make it more
manageable.
Below is a draft of a document that I plan to give to my introductory
C++ class.
Please note that I have purposely left out any mention of safety issues
in the ctors which could be resolved thru some combination smart
pointers or exception handling. These topics will be introduced in the
follow up course.
If anyone would like the complete document in MS Word (after I have
incorporated improvements you folks suggest), send me an e-mail with
"Deep versus Shallow Copy" as a title. The appearance in the Word doc
is much better (this tool is rather limited when it comes to formatting
choices and it seems to get the indentation wrong when translating from
Word).
PART 1:
Subject: Explanation of Deep versus Shallow Copy
Author: Bob Langelaan
Date: Nov. 25th, 2006
Note: The reader should already have studied the following C++
concepts in order to be able to understand this document: pointers and
references; the "new" and "delete" operators; the member
initialization list (MIL); the "this" pointer.
1.0 Introduction
Let us start with the following class:
class Example1
{
private:
int ii;
double dd;
ABC abc;
};
Now let us instantiate several Example1 objects:
Example1 xyz1, xyz2;
..
.. // Assume xyz1's value modified here
..
xyz2 = xyz1; // Is this allowed?
Yes, the above program statement is allowed in C++. The assignment
operator is normally automatically overloaded (see section "3.0
Advanced Topic Addendum" below for details on some exceptions) for
any class in C++. If the programmer does not overload the assignment
operator, the compiler will do it for you. Let us assume that in this
case the programmer has not explicitly overloaded the assignment
operator. What will the compiler supplied assignment operator
(sometimes called the "implicit" or "synthesized" assignment
operator) do in the statement above?
The ii member of xyz1 will be assigned to the ii member of xyz2; the dd
member of xyz1 will be assigned to the dd member of xyz2; and the abc
member of xyz1 will be assigned to the abc member of xyz2. This
process of assigning the members of one object to the members of
another object is called a "memberwise" assignment. Programmers
may also refer to this as a "shallow copy".
What if we have the following C++ statement?
Example1 xyz3 = xyz1; // Does this invoke the overloaded
// assignment operator as well?
No. In this case the copy constructor of the Example1 class will be
invoked. Just as with the overloaded assignment operator, if the
programmer does not supply a copy constructor for their class, the
compiler will. The compiler supplied copy constructor (the implicit or
synthesized copy constructor) does the equivalent to initializing all
of the members of the new object in its member initialization list
(MIL). This, in turn, will cause the copy constructor for each of the
members to be invoked. Therefore, in the previous C++ statement, the
compiler supplied copy constructor will effectively invoke the copy
constructor for each of the members of xyz3, passing to each copy
constructor the corresponding member of xyz1.
But what if we having the following class:
class Example2
{
public:
Example2();
private:
int ii;
double dd;
ABC * abcPtr; // will point to a dynamically created ABC object
};
And here is the implementation of the Example2 constructor:
Example2::Example2()
{
abcPtr = new ABC; // dynamically create an ABC object
// and have the class member abcPtr point to it.
.
.
.
}
Now let us instantiate several Example2 objects:
Example2 xyz1, xyz2;
..
.. // Assume xyz1's value modified here
..
xyz2 = xyz1; // Is a shallow copy good enough here?
The answer is very likely no. A shallow assignment will cause the
abcPtr member of both xyz1 and xyz2 to point to the same ABC object
(the ABC object that xyz1's abcPtr is pointing to). As well, with a
shallow assignment, the ABC object that xyz2's abcPtr was pointing to
is probably lost, thereby creating a memory leak! This is likely not
the result the programmer is trying to achieve.
In such a case we need to do a "non-memberwise" assignment.
Programmers may also refer to this as a "deep copy".
The assignment operator, instead of effectively doing this:
xyz1.abcPtr = xyz2.abcPtr; // this simply assigns one
// pointer to the other
pointer
needs to effectively do this:
*(xyz1.abcPtr) = *(xyz2.abcPtr); // This will take the ABC object
that
// xyz2.abcPtr is pointing to and
assign it to the
// ABC object that xyz1.abcPtr is
pointing to.
Therefore, in the case of class Example2, we need to overload the
assignment operator, and not rely on the compiler supplied assignment
operator. The programmer supplied assignment operator will need to do
a deep copy.
What if we have the following?
Example2 xyz3 = xyz1; // Can we use the compiler
// supplied copy constructor here?
No. The compiler supplied copy constructor will do a shallow copy. It
will effectively do this:
xyz3.abcPtr = xyz1.abcPtr; // this simply assigns one pointer
// to the other pointer
when what we need it to effectively do is:
xyz3.abcPtr = new ABC; // first dynamically create an ABC object
// (remember that xyz3 is constructed in this copy constructor)
*(xyz3.abcPtr) = *(xyz1.abcPtr); // then do the same as the
// programmer supplied assignment operator - a deep copy
Therefore, the programmer will need to supply a copy constructor for
the Example2 class as well. In a case such as this, the programmer
will also need to provide a destructor for their class. A C++ rule of
thumb, often referred to as the "Rule of Three", is that if your
class needs a destructor, then it likely needs a programmer defined
copy constructor and assignment operator as well. See the next section
below titled "2.0 Sample Code" for the implementation of all 3 of
these for the Example2 class.
will be simpler for people to provide feedback (by directly inserting
their comments in the post). I will post it in 3 parts to make it more
manageable.
Below is a draft of a document that I plan to give to my introductory
C++ class.
Please note that I have purposely left out any mention of safety issues
in the ctors which could be resolved thru some combination smart
pointers or exception handling. These topics will be introduced in the
follow up course.
If anyone would like the complete document in MS Word (after I have
incorporated improvements you folks suggest), send me an e-mail with
"Deep versus Shallow Copy" as a title. The appearance in the Word doc
is much better (this tool is rather limited when it comes to formatting
choices and it seems to get the indentation wrong when translating from
Word).
PART 1:
Subject: Explanation of Deep versus Shallow Copy
Author: Bob Langelaan
Date: Nov. 25th, 2006
Note: The reader should already have studied the following C++
concepts in order to be able to understand this document: pointers and
references; the "new" and "delete" operators; the member
initialization list (MIL); the "this" pointer.
1.0 Introduction
Let us start with the following class:
class Example1
{
private:
int ii;
double dd;
ABC abc;
};
Now let us instantiate several Example1 objects:
Example1 xyz1, xyz2;
..
.. // Assume xyz1's value modified here
..
xyz2 = xyz1; // Is this allowed?
Yes, the above program statement is allowed in C++. The assignment
operator is normally automatically overloaded (see section "3.0
Advanced Topic Addendum" below for details on some exceptions) for
any class in C++. If the programmer does not overload the assignment
operator, the compiler will do it for you. Let us assume that in this
case the programmer has not explicitly overloaded the assignment
operator. What will the compiler supplied assignment operator
(sometimes called the "implicit" or "synthesized" assignment
operator) do in the statement above?
The ii member of xyz1 will be assigned to the ii member of xyz2; the dd
member of xyz1 will be assigned to the dd member of xyz2; and the abc
member of xyz1 will be assigned to the abc member of xyz2. This
process of assigning the members of one object to the members of
another object is called a "memberwise" assignment. Programmers
may also refer to this as a "shallow copy".
What if we have the following C++ statement?
Example1 xyz3 = xyz1; // Does this invoke the overloaded
// assignment operator as well?
No. In this case the copy constructor of the Example1 class will be
invoked. Just as with the overloaded assignment operator, if the
programmer does not supply a copy constructor for their class, the
compiler will. The compiler supplied copy constructor (the implicit or
synthesized copy constructor) does the equivalent to initializing all
of the members of the new object in its member initialization list
(MIL). This, in turn, will cause the copy constructor for each of the
members to be invoked. Therefore, in the previous C++ statement, the
compiler supplied copy constructor will effectively invoke the copy
constructor for each of the members of xyz3, passing to each copy
constructor the corresponding member of xyz1.
But what if we having the following class:
class Example2
{
public:
Example2();
private:
int ii;
double dd;
ABC * abcPtr; // will point to a dynamically created ABC object
};
And here is the implementation of the Example2 constructor:
Example2::Example2()
{
abcPtr = new ABC; // dynamically create an ABC object
// and have the class member abcPtr point to it.
.
.
.
}
Now let us instantiate several Example2 objects:
Example2 xyz1, xyz2;
..
.. // Assume xyz1's value modified here
..
xyz2 = xyz1; // Is a shallow copy good enough here?
The answer is very likely no. A shallow assignment will cause the
abcPtr member of both xyz1 and xyz2 to point to the same ABC object
(the ABC object that xyz1's abcPtr is pointing to). As well, with a
shallow assignment, the ABC object that xyz2's abcPtr was pointing to
is probably lost, thereby creating a memory leak! This is likely not
the result the programmer is trying to achieve.
In such a case we need to do a "non-memberwise" assignment.
Programmers may also refer to this as a "deep copy".
The assignment operator, instead of effectively doing this:
xyz1.abcPtr = xyz2.abcPtr; // this simply assigns one
// pointer to the other
pointer
needs to effectively do this:
*(xyz1.abcPtr) = *(xyz2.abcPtr); // This will take the ABC object
that
// xyz2.abcPtr is pointing to and
assign it to the
// ABC object that xyz1.abcPtr is
pointing to.
Therefore, in the case of class Example2, we need to overload the
assignment operator, and not rely on the compiler supplied assignment
operator. The programmer supplied assignment operator will need to do
a deep copy.
What if we have the following?
Example2 xyz3 = xyz1; // Can we use the compiler
// supplied copy constructor here?
No. The compiler supplied copy constructor will do a shallow copy. It
will effectively do this:
xyz3.abcPtr = xyz1.abcPtr; // this simply assigns one pointer
// to the other pointer
when what we need it to effectively do is:
xyz3.abcPtr = new ABC; // first dynamically create an ABC object
// (remember that xyz3 is constructed in this copy constructor)
*(xyz3.abcPtr) = *(xyz1.abcPtr); // then do the same as the
// programmer supplied assignment operator - a deep copy
Therefore, the programmer will need to supply a copy constructor for
the Example2 class as well. In a case such as this, the programmer
will also need to provide a destructor for their class. A C++ rule of
thumb, often referred to as the "Rule of Three", is that if your
class needs a destructor, then it likely needs a programmer defined
copy constructor and assignment operator as well. See the next section
below titled "2.0 Sample Code" for the implementation of all 3 of
these for the Example2 class.