Shallow/deep copy

Hello, I have a small code here which results in an error message.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Dummy {
public:
    int *num;
    int test;
    Dummy() {
        num = new int{0};
    }
    ~Dummy() {
        delete num;
    }
};

void dummyTest() {
    Dummy a;
    *a.num = 4;
    Dummy b{a};
    
    cout << "a: " << *a.num << endl;
    cout << "b: " << *b.num << endl;
}

when I run dummyTest() from main I get a "pointer being freed was not allocated"-error. Why?
It looks like you need to look up the rule of 3(5). You have a pointer in your class and a constructor and destructor but no copy constructor.

https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)
Thanks, but can you explain why it happens? I know that it has to do with a lack of copy constructor, but I am trying to understand what exactly happens.
First, num points to dynamic ("heap") memory (new int{0}).
Then, you change num to point to automatic ("stack") memory (*a.num = 4).

Finally, your destructor tried to delete the memory that num points to, but it no longer points to dynamic memory, so you get an error. You can only delete memory use allocate with new.

Your issue is a combination of two problems, as the others pointed out. Just to be clear: Even if you didn't have the b object, you would still have the issue I just stated.


I don't know what I was thinking, kyrresc. :)
Just listen to coder777.
Last edited on
The point of the shallow copy on line 16 is that you copy pointer. So after that both objects a and b are seeing the the same pointer. Thus both trying to free them. The destructor of the object which is called later will try to delete an already freed memory.
Thanks! just to be sure: when writing
1
2
Dummy a;
 *a.num = 4;

are you here de-referencing the num-pointer, giving it a value 4? I was just curious as it with this syntax "looks" like a de-referencing of the a-object.
Last edited on
[Removed dumb sentence to not confuse myself and others. The rest of this post is accurate]

Yes it's the same as *(a.num) = 4;

I can see how that can be confusing though. Check out https://en.cppreference.com/w/cpp/language/operator_precedence

The precedence of member access (.) is higher than the precedence of dereferencing.
(Also, the associativity of the dereferencing operator is right-to-left, so something like *p++ is interpreted as *(p++) as well, where ++ and * have the same precedence).

Precedence rules are derived from the rules of the language/syntax itself. If you're ever not sure of a precedence rule, adding more parenthesis doesn't hurt unless you are gratuitous to the point where it hurts readability.

PS: Hypothetically, if the a object were a pointer that could be dereferenced,
it would have to be done like: a->num; or (*a).num;.
Last edited on
@Ganado
Your first post was just plain wrong. Your second is ok.
Thanks... I was messing up something in my head, thinking that the re-assignment no longer pointed to dynamic memory. But that's wrong because the same memory is being used, it just has a 4 in it rather than a 0 now.
Last edited on
This
Then, you change num to point to automatic ("stack") memory (*a.num = 4).
is wrong. The pointer is not changed. The 4 is copied into the provided dynamic memory.

You are right about the precedence. So I wonder why you claim that the pointer is changed. Actually there is no pointer operation.

Since a is not a pointer (*a).num would be a compiler error.
I have now implemented a copy constructor and an assignment operator for my class. Do these two, i.e. the copy constructor and the assignment operator "do the same"? Just that

1
2
3
4
Dummy a;
Dummy b{a}; //copy constructor called??
Dummy c{a};
 c = a; //assignment operator called?? 

Constructor constructs a new object.
Assignment modifies existing object.

1
2
3
4
5
6
7
8
std::string lotr = /*the entire text of the Lord of the Rings books*/
// assert: lotr is a very long string

std::string twilight = "hello";
// assert: twilight.size() == 5

lotr = twilight; // copy assignment
// assert: lotr.size() == 5 

The assignment does not just make the lotr to contain "hello". It has to get rid of a huge text first.


EDIT:
Lets repeat your original error (and some) without classes:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int* Anum = new int {0}; // default construct A
// Lets call the dynamically allocated int object "HeapInt"
// Anum points to HeapInt

int* Bnum = Anum; // copy construct B; initialize with A
// Bnum points to HeapInt. This is shallow copy

int* Cnum = new int {0}; // default construct C
// Cnum points to "VictimOfAssign"

Cnum = Anum; // copy assignment
// nobody points to VictimOfAssign. This is memory leak
// Cnum points to HeapInt. This is shallow copy

delete Cnum; // C destructor deallocates HeapInt
delete Bnum; // B destructor; error - pointed to address is not allocated
delete Anum; // A destructor; error - pointed to address is not allocated
// VictimOfAssign still exists, but is unreachable 

And then with deep copy:
1
2
3
4
5
6
7
8
9
10
11
int* Anum = new int {0};

int* Bnum = new int {*Anum}; // copy construct B; initialize with A's data

int* Cnum = new int {0};

*Cnum = *Anum; // copy assignment

delete Cnum; // C destructor deallocates the int that C points to
delete Bnum; // B destructor deallocates the int that B points to
delete Anum; // A destructor deallocates the int that A points to 
Last edited on
Topic archived. No new replies allowed.