0% found this document useful (0 votes)
10K views7 pages

9.15 - Shallow vs. Deep Copying: #Include #Include

The document discusses shallow copying versus deep copying in C++. Shallow copying, which is the default, copies the values of members but not dynamically allocated memory, which can cause problems. Deep copying allocates new memory and fully copies the object to avoid these issues.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10K views7 pages

9.15 - Shallow vs. Deep Copying: #Include #Include

The document discusses shallow copying versus deep copying in C++. Shallow copying, which is the default, copies the values of members but not dynamically allocated memory, which can cause problems. Deep copying allocates new memory and fully copies the object to avoid these issues.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 7

3/4/2019 9.15 — Shallow vs.

deep copying | Learn C++

9.15 — Shallow vs. deep copying


BY ALEX ON NOVEMBER 9TH, 2007 | LAST MODIFIED BY ALEX ON AUGUST 25TH, 2018

Shallow copying

Because C++ does not know much about your class, the default copy constructor and default assignment operators it provides use a copying
method known as a memberwise copy (also known as a shallow copy). This means that C++ copies each member of the class individually (using
the assignment operator for overloaded operator=, and direct initialization for the copy constructor). When classes are simple (e.g. do not contain
any dynamically allocated memory), this works very well.

For example, let’s take a look at our Fraction class:

1 #include <cassert>
2 #include <iostream>
3
4 class Fraction
5 {
6 private:
7     int m_numerator;
8     int m_denominator;
9
10 public:
11     // Default constructor
12     Fraction(int numerator=0, int denominator=1) :
13         m_numerator(numerator), m_denominator(denominator)
14     {
15         assert(denominator != 0);
16     }
17
18     friend std::ostream& operator<<(std::ostream& out, const Fraction &f1);
19 };
20
21 std::ostream& operator<<(std::ostream& out, const Fraction &f1)
22 {
23 out << f1.m_numerator << "/" << f1.m_denominator;
24 return out;
25 }

The default copy constructor and assignment operator provided by the compiler for this class look something like this:

1 #include <cassert>
2 #include <iostream>
3
4 class Fraction
5 {
6 private:
7     int m_numerator;
8     int m_denominator;
9
10 public:
11     // Default constructor
12     Fraction(int numerator=0, int denominator=1) :
13         m_numerator(numerator), m_denominator(denominator)
14     {
15         assert(denominator != 0);
16     }
17
18     // Copy constructor
19     Fraction(const Fraction &f) :
20         m_numerator(f.m_numerator), m_denominator(f.m_denominator)
21     {
22     }
23  
24     Fraction& operator= (const Fraction &fraction);
25  
26     friend std::ostream& operator<<(std::ostream& out, const Fraction &f1);
27 };
28
29 std::ostream& operator<<(std::ostream& out, const Fraction &f1)
30 {
31 out << f1.m_numerator << "/" << f1.m_denominator;
https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/ 1/7
3/4/2019 9.15 — Shallow vs. deep copying | Learn C++
32 return out;
33 }
34  
35 // A better implementation of operator=
36 Fraction& Fraction::operator= (const Fraction &fraction)
37 {
38     // self-assignment guard
39     if (this == &fraction)
40         return *this;
41
42     // do the copy
43     m_numerator = fraction.m_numerator;
44     m_denominator = fraction.m_denominator;
45
46     // return the existing object so we can chain this operator
47     return *this;
48 }

Note that because these default versions work just fine for copying this class, there’s really no reason to write our own version of these functions in
this case.

However, when designing classes that handle dynamically allocated memory, memberwise (shallow) copying can get us in a lot of trouble! This is
because shallow copies of a pointer just copy the address of the pointer -- it does not allocate any memory or copy the contents being pointed to!

Let’s take a look at an example of this:

1 #include <cstring> // for strlen()


2 #include <cassert> // for assert()
3  
4 class MyString
5 {
6 private:
7     char *m_data;
8     int m_length;
9
10 public:
11     MyString(const char *source="")
12     {
13         assert(source); // make sure source isn't a null string
14  
15         // Find the length of the string
16         // Plus one character for a terminator
17         m_length = std::strlen(source) + 1;
18         
19         // Allocate a buffer equal to this length
20         m_data = new char[m_length];
21         
22         // Copy the parameter string into our internal buffer
23         for (int i=0; i < m_length; ++i)
24             m_data[i] = source[i];
25     
26         // Make sure the string is terminated
27         m_data[m_length-1] = '\0';
28     }
29
30     ~MyString() // destructor
31     {
32         // We need to deallocate our string
33         delete[] m_data;
34     }
35
36     char* getString() { return m_data; }
37     int getLength() { return m_length; }
38 };

The above is a simple string class that allocates memory to hold a string that we pass in. Note that we have not defined a copy constructor or
overloaded assignment operator. Consequently, C++ will provide a default copy constructor and default assignment operator that do a shallow
copy. The copy constructor will look something like this:

1 MyString::MyString(const MyString &source) :


2     m_length(source.m_length), m_data(source.m_data)
3 {
4 }

https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/ 2/7
3/4/2019 9.15 — Shallow vs. deep copying | Learn C++
Note that m_data is just a shallow pointer copy of source.m_data, meaning they now both point to the same thing.

Now, consider the following snippet of code:

1 int main()
2 {
3     MyString hello("Hello, world!");
4     {
5         MyString copy = hello; // use default copy constructor
6     } // copy is a local variable, so it gets destroyed here.  The destructor deletes copy's string, which leaves
hello with a dangling pointer
7  
8     std::cout << hello.getString() << '\n'; // this will have undefined behavior
9  
10     return 0;
11 }

While this code looks harmless enough, it contains an insidious problem that will cause the program to crash! Can you spot it? Don’t worry if you
can’t, it’s rather subtle.

Let’s break down this example line by line:

1     MyString hello("Hello, world!");

This line is harmless enough. This calls the MyString constructor, which allocates some memory, sets hello.m_data to point to it, and then copies
the string “Hello, world!” into it.

1     MyString copy = hello; // use default copy constructor

This line seems harmless enough as well, but it’s actually the source of our problem! When this line is evaluated, C++ will use the default copy
constructor (because we haven’t provided our own). This copy constructor will do a shallow copy, initializing copy.m_data to the same address of
hello.m_data. As a result, copy.m_data and hello.m_data are now both pointing to the same piece of memory!

1 } // copy gets destroyed here

When copy goes out of scope, the MyString destructor is called on copy. The destructor deletes the dynamically allocated memory that both
copy.m_data and hello.m_data are pointing to! Consequently, by deleting copy, we’ve also (inadvertently) affected hello. Variable copy then gets
destroyed, but hello.m_data is left pointing to the deleted (invalid) memory!

1     std::cout << hello.getString() << '\n'; // this will have undefined behavior

Now you can see why this program has undefined behavior. We deleted the string that hello was pointing to, and now we are trying to print the
value of memory that is no longer allocated.

The root of this problem is the shallow copy done by the copy constructor -- doing a shallow copy on pointer values in a copy constructor or
overloaded assignment operator is almost always asking for trouble.

Deep copying

One answer to this problem is to do a deep copy on any non-null pointers being copied. A deep copy allocates memory for the copy and then
copies the actual value, so that the copy lives in distinct memory from the source. This way, the copy and source are distinct and will not affect
each other in any way. Doing deep copies requires that we write our own copy constructors and overloaded assignment operators.

Let’s go ahead and show how this is done for our MyString class:

1 // Copy constructor
2 MyString::MyString(const MyString& source)
3 {
4     // because m_length is not a pointer, we can shallow copy it
5     m_length = source.m_length;
6  
7     // m_data is a pointer, so we need to deep copy it if it is non-null
8     if (source.m_data)
9     {
10         // allocate memory for our copy
11         m_data = new char[m_length];
12  
13         // do the copy
14         for (int i=0; i < m_length; ++i)
15             m_data[i] = source.m_data[i];
16     }
17     else
18         m_data = 0;
19 }

https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/ 3/7
3/4/2019 9.15 — Shallow vs. deep copying | Learn C++
As you can see, this is quite a bit more involved than a simple shallow copy! First, we have to check to make sure source even has a string (line
8). If it does, then we allocate enough memory to hold a copy of that string (line 11). Finally, we have to manually copy the string (lines 14 and 15).

Now let’s do the overloaded assignment operator. The overloaded assignment operator is slightly trickier:

1 // Assignment operator
2 MyString& MyString::operator=(const MyString & source)
3 {
4     // check for self-assignment
5     if (this == &source)
6         return *this;
7  
8     // first we need to deallocate any value that this string is holding!
9     delete[] m_data;
10  
11     // because m_length is not a pointer, we can shallow copy it
12     m_length = source.m_length;
13  
14     // m_data is a pointer, so we need to deep copy it if it is non-null
15     if (source.m_data)
16     {
17         // allocate memory for our copy
18         m_data = new char[m_length];
19  
20         // do the copy
21         for (int i=0; i < m_length; ++i)
22             m_data[i] = source.m_data[i];
23     }
24     else
25         m_data = 0;
26  
27     return *this;
28 }

Note that our assignment operator is very similar to our copy constructor, but there are three major differences:

We added a self-assignment check.


We return *this so we can chain the assignment operator.
We need to explicitly deallocate any value that the string is already holding (so we don’t have a memory leak when m_data is reallocated
later).

When the overloaded assignment operator is called, the item being assigned to may already contain a previous value, which we need to make
sure we clean up before we assign memory for new values. For non-dynamically allocated variables (which are a fixed size), we don’t have to
bother because the new value just overwrite the old one. However, for dynamically allocated variables, we need to explicitly deallocate any old
memory before we allocate any new memory. If we don’t, the code will not crash, but we will have a memory leak that will eat away our free
memory every time we do an assignment!

A better solution

Classes in the standard library that deal with dynamic memory, such as std::string and std::vector, handle all of their memory management, and
have overloaded copy constructors and assignment operators that do proper deep copying. So instead of doing your own memory management,
you can just initialize or assign them like normal fundamental variables! That makes these classes simpler to use, less error-prone, and you don’t
have to spend time writing your own overloaded functions!

Summary

The default copy constructor and default assignment operators do shallow copies, which is fine for classes that contain no dynamically
allocated variables.
Classes with dynamically allocated variables need to have a copy constructor and assignment operator that do a deep copy.
Favor using classes in the standard library over doing your own memory management.

9.x -- Chapter 9 comprehensive quiz

Index

9.14 -- Overloading the assignment operator

https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/ 4/7
3/4/2019 9.15 — Shallow vs. deep copying | Learn C++

C++ TUTORIAL | PRINT THIS POST

75 comments to 9.15 — Shallow vs. deep copying

« Older Comments 1 2

Asgar
February 19, 2019 at 9:21 am · Reply

Hi.

About this line in the first paragraphs:


"... C++ copies each member of the class individually (using the assignment operator for overloaded operator=, and direct initialization for the
copy constructor)."

Did you mean to say "initialization" rather than "direct initialization"? As I understand, a copy constructor is invoked during any of the 3 kinds of
initialization:
1. copy initialization,
2. direct initialization, and
3. uniform initialization

Alex
February 19, 2019 at 8:34 pm · Reply

In this context, we're talking about copying the _members_ of a class. These members are initialized using direct
initialization (as opposed to copy initialization).

Jon
January 29, 2019 at 3:18 am · Reply

Hello again, I can't quite figure something out - for the following code snippet you wrote:

1 int main()
2 {
3     MyString hello("Hello, world!");
4     {
5         MyString copy = hello; // use default copy constructor
6     } // copy is a local variable, so it gets destroyed here.  The destructor deletes copy's string, which lea
ves hello with a dangling pointer
7
8     std::cout << hello.getString() << '\n'; // this will have undefined behavior
9
10     return 0;
11 }

If I delete the brackets on lines 4 and 6, the program compiles, but I get the runtime error: Test(50460,0x10039b380) malloc: *** error for object
0x100403700: pointer being freed was not allocated.

I was under the impression that this would run OK because the variables would all stay in scope until the end of the program and because
deleting a null pointer has no effect (is that not what this error message is pointing to?) but it looks like I'm missing something. It does print out
"Hello World!" but something is not quite right...

Jon
January 29, 2019 at 3:45 am · Reply

Think I've got it, I believe I conflated null pointer and uninitialized pointer?

nascardriver
January 29, 2019 at 8:48 am · Reply

All pointers in this example have been initialized, neither is a nullptr.


Both @hello and @copy point to the same char array. Once one of @hello or @copy goes out of scope, the char
array will be deleted. The other variable will still point to were the char array used to be. When the other variable goes out of
scope, the char array will be deleted again, but it doesn't exist. Behavior is undefined.

https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/ 5/7
3/4/2019 9.15 — Shallow vs. deep copying | Learn C++

nascardriver
August 23, 2018 at 9:22 am · Reply

Hi Alex!

Third code block, line 17 should use @std::strlen

Alex
August 25, 2018 at 8:34 am · Reply

Fixed! Thanks!

seriouslysupersonic
August 23, 2018 at 9:15 am · Reply

Hi!

1) Since

1 MyString(const char *source="")


2     {
3         assert(source); // make sure source isn't a null string

aren't we sure that we will never be copying / assigning a non-null string (if the allocation was successful)?

2) Shouldn't we assign @m_length after allocating new memory and checking @m_data is non-null?

3) Is there a reason not to use strcpy() and use a for loop?

nascardriver
August 23, 2018 at 9:19 am · Reply

Hi!

1)

1 MyString(nullptr);

2)
The order doesn't matter

3)
No

seriouslysupersonic
August 23, 2018 at 9:40 am · Reply

Thank you for the quick reply.

1) If we do that, wouldn't the assertion fail and we wouldn't be able to copy / assign a null source.m_data field - because (unless
memory allocation failed) no object with a null m_data field would be created? Shouldn't we check instead that new didn't fail?

2) But if @source.m_data is null because the memory allocation of the source object failed, we are assigning whatever length the
source believes it successfully allocated when the source object was created while we assign a nullptr to @m_data.

nascardriver
August 23, 2018 at 9:53 am · Reply

1) There is not @source object, @source is a const char*. I might have misunderstood your question. Can you
elaborate on (1)?
> Shouldn't we check instead that new didn't fail?
All we can do is check for nullptr. If allocation fails without the noexcept version of @new, an exception is thrown and this
constructor won't be reached anyway.

2) If @source failed to allocate memory, it will have thrown an exception and you shouldn't be able to use the source object
anymore. But I agree with you, setting the length after verifying that there actually is data is better.

https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/ 6/7
3/4/2019 9.15 — Shallow vs. deep copying | Learn C++

seriouslysupersonic
August 23, 2018 at 10:08 am · Reply

1) I think I wasn't too clear in the beginning (sorry for my English). What I meant was: every MyString
object seems to have a non-null m_data field because we either assert that in the const char* constructor
or, as you just explained, if new fails, the constructor won't be reached anyway. If that's true, why do we then check

1 if (source.m_data)

in the copy constructor and assignment operator overload?

2) This question doesn't make much sense because I forgot new would throw an exception and then we would have to
handle that. Thanks for the explanation!

nascardriver
August 23, 2018 at 10:16 am · Reply

> why do we then check


Seems redundant. But if you were to add another constructor or function that allows @m_data to be a
nullptr, this constructor wouldn't break because of that update.

« Older Comments 1 2

https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/ 7/7

You might also like