Providing a Copy Constructor
I want MORE constructors!!! I want to be able to copy
Barn
s!Okay, but remember, you only need to write a copy constructor if someone is going to use it. It's a waste of time to write code no one wants to use.
But MORE is better!!
Can we just say
= default
and let the system synthesize it?No! If the compiler synthesizes a copy constructor for us, the code will do the wrong thing!
If I write it, it'll probably be wrong.
And that's one reason not to write code you don't need to write.
Recall that job of a copy constructor is to initialize a new instance of a class to look exactly like a preexisting instance of the class.
C++ can synthesize a copy constructor for us. It'll do that if we say = default
(and it'll also usually do so if we if we do't specify anything).
C++'s synthesized copy constructor will make a copy of each of the data members in the class, one at a time. In some cases, this is great (it works fine in our Cow
class, for example), but sometimes it's totally wrong. Let's see why…
As a reminder, here's what a Barn
with two Cow
s looks like in memory.
Let's take a look at what would happen with a synthesized copy constructor:
Beware of Shallow Copies!
The kind of copy done by the synthesized copy constructor is called a shallow copy.
At the surface, it looks like our Barn
was copied, but if we dig any deeper than the data members themselves, we will find that the two objects actually share memory! This arrangement violates the principle of ownership—only one Barn
can own the cows, but now two Barn
s both think they own the same cows!
Making a Deep Copy
To fix this issue, we need to write our own copy constructor that will create a deep copy.
A deep copy will go through and make copies of all of the heap memory being used by the Barn
object, so that we'll have a truly separate Barn
with separate cows!
Barn::Barn(const Barn& other) :
cowCapacity_{other.cowCapacity_},
cowCount_{other.cowCount_},
residentCows_{new Cow*[cowCapacity_]}
{
for (size_t i = 0; i < other.cowCount_; ++i) {
// Fetch pointer to the Cow we want to copy from their array
Cow* otherCowPtr = other.residentCows_[i];
// Make a copy.
Cow* duplicateCowPtr = new Cow{*otherCowPtr};
// Store it in our array.
residentCows_[i] = duplicateCowPtr;
}
}
You could also write the loop as follows, using the start/past-the-end pointer approach (that we saw in Week 5 Lesson 1):
{
for (Cow** p = other.residentCows_; p < other.residentCows_ + other.cowCount_; ++p) {
// Fetch pointer to the Cow we want to copy from their array
Cow* otherCowPtr = *p;
// Make a copy.
Cow* duplicateCowPtr = new Cow{*otherCowPtr};
// Store it in our array.
residentCows_[i] = duplicateCowPtr;
}
}
The inside of the for
loop is going to look a lot like our addCow
function, except that we're not updating the cowCount_
. By starting with a cowCount_
of 0
, we can leverage the function we already wrote:
Barn::Barn(const Barn& other) :
cowCapacity_{other.cowCapacity_},
cowCount_{0},
residentCows_{new Cow*[cowCapacity_]}
{
for (size_t i = 0; i < other.cowCount_; ++i) {
// Fetch pointer to the Cow we want to copy from the array
Cow* otherCowPtr = other.residentCows_[i];
// Make a copy.
addCow(*otherCowPtr);
}
}
Alternatively, you could write the loop using the start/past-the-end pointer approach (that we saw in Week 5 Lesson 1):
{
for (Cow** p = other.residentCows_; p < other.residentCows_ + other.cowCount_; ++p) {
// Fetch pointer to the Cow we want to copy from the array
Cow* otherCowPtr = *p;
// Make a copy.
addCow(*otherCowPtr);
}
In that second loop, could I have skipped defining
otherCowPtr
and just writtenaddCow(**p);
?Yes, if you wanted.
And it would be more efficient?
No. The compiler isn't stupid. When optimization is turned on, just about any way we'd write these loops will probably produce the exact same machine code.
Write your code so it is as easy to understand as possible.
I think
addCow(**otherCowPtr);
is easy to understand.I don't.
Tip: Whenever you have code that would put two stars together, you can usually rewrite it to be less confusing by adding a variable explaining what is going on.
On Online GDB is a slightly more elegant version of the code that reuses our existing code even more.
Can I have MORE video explanation of this stuff?
And can we do it my short-and-sweet way?
You're in luck, Duck. Remaking this old video is on our to-do list, but for now, you get your way. Sorry Hedgehog.
I don't mind. This way I get to see my friend Prof. Medero.
Any Privacy Concerns?
Whoah, whoah! Wait a second!
What's up?
We can't even access
other.residentCows_
, can we? It's private! Can we access the private contents of a different object?Ah… well, private access means that no code outside the class can access that member. Objects that are instances of the same class can access each others' private stuff.
In this case,
other
is aBarn
and this function is in theBarn
class, so it's fine.
(When logged in, completion status appears here.)