Copy Constructor

1. Does copy constructor can copy all public, private, and protected members?

2. Does copy constructor come in handy only when there is a pointer in the class??

3. I don't really understand the difference of deep copy and shallow copy in how they are writen.
Can somebody give me an example about the difference about it??

Thanks..
1. Yes.

2. No: https://www.geeksforgeeks.org/copy-constructor-in-cpp/

3. Shallow copy is what happens in the example in the link. You've got some variables, you copy their values, and move on with your life.

However, lets say you have a pointer, now all you've done is copy the pointer and not the actual memory it was pointing at. Therefore, if you now have two pointers looking at the same memory, which probably isn't what you want (changing one variable would change the other).

A deep copy will allow you to allocated separate memory and copy those values into it.

https://www.learncpp.com/cpp-tutorial/915-shallow-vs-deep-copying/
1. public/private/protected has no bearing on the compiler-generated copy constructor, so everything will be copied by value in the same way.

2. Pointers are big part of it, but it isn't just pointers. For example, someone might want to make a wrapper around OpenGL Textures. There aren't any pointers there, but there are integers that work as handles to the GPU texture. The programmer still has to ask themselves the question "when I copy my Texture object, do I want the GPU-side texture information to also be copied"? Basically, you need to make sure you're handling external resources correctly, as well as dynamically allocated memory (which are pointed to by pointers).

3.
"Shallow copy" = you're copying the handle/pointer to the data, without actually copying the data itself. Imagine an shortcut on your computer desktop. You can copy this shortcut as many times as you'd like. They all will still direct you to the same program. i.e. Both pointers will still point to the same underlying data after a shallow copy.

"Deep copy" = you're copying the underlying data accessible from the handle/pointer, so that you can now work with 2x different copies, and change those copies independently.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// Contrived example program
#include <iostream>
#include <string>

/// SHALLOW COPY
class IntWrapper {
  public:
    IntWrapper(int n)
    : int_ptr(new int{n}) { }        

    ~IntWrapper()
    {
        delete int_ptr;
        int_ptr = nullptr;
    }
    
    int getInt()
    {
        return *int_ptr;   
    }
    
    void setInt(int value)
    {
        *int_ptr = value;   
    }
    
  private:
    int* int_ptr;    
};

///  DEEP COPY
class IntWrapperWithCopy {    
  public:
    IntWrapperWithCopy(int n)
    : int_ptr(new int{n})
    {
        
    }
    
    ~IntWrapperWithCopy()
    {
        delete int_ptr;
        int_ptr = nullptr;
    }
    
    /// PERFORMS DEEP COPY
    IntWrapperWithCopy(const IntWrapperWithCopy& iw)
    : int_ptr(new int{*iw.int_ptr}) { }
    
    int getInt()
    {
        return *int_ptr;   
    }
    
    void setInt(int value)
    {
        *int_ptr = value;   
    }
    
  private:
    int* int_ptr;     
};

int main()
{
    using std::cout;
    
    IntWrapper iw(3);
    cout << "original IntWrapper = " << iw.getInt() << '\n';
    
    IntWrapper iw_copy = iw;
    iw_copy.setInt(5);
    
    cout << "original IntWrapper after copy = " << iw.getInt() << '\n';
    cout << "copied IntWrapper after copy = " << iw_copy.getInt() << '\n'; 
    
    cout << "\n\n\n";
    
    IntWrapperWithCopy iwc(3);
    cout << "original IntWrapperWithCopy = " << iwc.getInt() << '\n';
    
    IntWrapperWithCopy iwc_copy = iwc;
    iwc_copy.setInt(5);
    
    cout << "original IntWrapperWithCopy after copy = " << iwc.getInt() << '\n';
    cout << "copied IntWrapperWithCopy after copy = " << iwc_copy.getInt() << '\n';  
}

original IntWrapper = 3
original IntWrapper after copy = 5 // ORIGINAL changed because it's a shallow copy
copied IntWrapper after copy = 5



original IntWrapperWithCopy = 3
original IntWrapperWithCopy after copy = 3 // ORIGINAL stayed the same -- DEEP copy.
copied IntWrapperWithCopy after copy = 5

<may crash during destruction>

See also: "rule of three"/"rule of five" for constructors.
https://en.cppreference.com/w/cpp/language/rule_of_three

Note that my example's IntWrapper (without deep copy) also has an issue with the one where both point to the same memory, because you end up deleting it twice, causing corruption. This is where it becomes important to know who owns the memory, and who doesn't. One "solution" would be to have another variable (a bool perhaps) to determine whether or not the current object owns the data it points to. The original object would be in charge of deleting it only if this flag is true.

e.g.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// Example program
#include <iostream>
#include <string>

class IntWrapper {
  public:
    IntWrapper(int n)
    : int_ptr(new int{n}), owner(true) { }        

    IntWrapper(const IntWrapper& iw)
    : int_ptr(iw.int_ptr), owner(false) { }

    ~IntWrapper()
    {
        if (owner)
            delete int_ptr;
    }
    
    int getInt()
    {
        return *int_ptr;   
    }
    
    void setInt(int value)
    {
        *int_ptr = value;   
    }
    
  private:
    int* int_ptr;    
    bool owner;
};

int main()
{
    using std::cout;
    
    IntWrapper iw(3);
    cout << "original IntWrapper = " << iw.getInt() << '\n';
    
    IntWrapper iw_copy = iw;
    iw_copy.setInt(5);
    
    cout << "original IntWrapper after copy = " << iw.getInt() << '\n';
    cout << "copied IntWrapper after copy = " << iw_copy.getInt() << '\n'; 
}

... but then this can go awry for a more complex situation, because if the copy's scope outlives the original's scope, you are now pointing to invalid data! Headaches... sharing data that needs to be cleaned up actually becomes more complicated than simply copying it.

This is where std::shared_ptr comes into play, for cases like this. Basically, reference counting happens, and only when there are no more references is the actual data destroyed.
Last edited on
Topic archived. No new replies allowed.