Shallow Copy && Deep Copy

In programming, copying objects is a common operation, but it's essential to understand the differences between shallow copy and deep copy to avoid unintended side effects, especially when dealing with complex data structures containing pointers.

What is a Shallow Copy?

A shallow copy of an object duplicates only the object's immediate members. If the object contains pointers or references to other objects, the shallow copy will copy the pointers or references, but not the objects they point to. This means both the original and the copied object will share the same referenced objects.

Example in C:
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int x;
    int y;
    int* ptr;
} Point;

int main() {
    Point p1;
    p1.x = 1;
    p1.y = 2;
    p1.ptr = (int*)malloc(sizeof(int));
    *(p1.ptr) = 10;

    // Shallow copy
    Point p2 = p1;

    printf("p2.x = %d, p2.y = %d, *p2.ptr = %d\n", p2.x, p2.y, *(p2.ptr));

    // Modifying p1.ptr will also affect p2.ptr
    *(p1.ptr) = 20;
    printf("After modifying p1.ptr, *p2.ptr = %d\n", *(p2.ptr));

    free(p1.ptr); // Free memory for both p1 and p2 since they share the same pointer
    return 0;
}
Output:
p2.x = 1, p2.y = 2, *p2.ptr = 10
After modifying p1.ptr, *p2.ptr = 20

The memory layout look like this:

p1:  +------+------+------+
     |  x   |  y   | ptr  |
     +------+------+------+
     |  1   |  2   |  *---|----> 10 (in heap memory)
     +------+------+------+

p2:  +------+------+------+
     |  x   |  y   | ptr  |
     +------+------+------+
     |  1   |  2   |  *---|----> 10 (same heap memory as p1)
     +------+------+------+

Here, both p1 and p2 point to the same memory location for ptr. Modifying the value pointed by p1.ptr will also affect p2.ptr because they share the same memory.

Example in C++:
#include <iostream>

class Point {
public:
    int x, y;
    int* ptr;

    Point(int xVal, int yVal, int ptrVal) : x(xVal), y(yVal) {
        ptr = new int(ptrVal);
    }

    // Shallow copy constructor
    Point(const Point& p) : x(p.x), y(p.y), ptr(p.ptr) {}

    void display() const {
        std::cout << "x = " << x << ", y = " << y << ", *ptr = " << *ptr << std::endl;
    }

    ~Point() {
        delete ptr;
    }
};

int main() {
    Point p1(1, 2, 10);
    Point p2 = p1; // Shallow copy

    p1.display();
    p2.display();

    *p1.ptr = 20;
    p1.display();
    p2.display(); // p2.ptr is also affected

    return 0;
}
Output:
x = 1, y = 2, *ptr = 10
x = 1, y = 2, *ptr = 10
x = 1, y = 2, *ptr = 20
x = 1, y = 2, *ptr = 20

What is a Deep Copy?

A deep copy duplicates not only the object itself but also all objects referenced by the original object. This means the copied object and the original object do not share any referenced objects; they are completely independent of each other.

Example in C:
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int x;
    int y;
    int* ptr;
} Point;

void deepCopyPoint(Point* dest, const Point* src) {
    dest->x = src->x;
    dest->y = src->y;
    dest->ptr = (int*)malloc(sizeof(int));
    *(dest->ptr) = *(src->ptr);
}

int main() {
    Point p1;
    p1.x = 1;
    p1.y = 2;
    p1.ptr = (int*)malloc(sizeof(int));
    *(p1.ptr) = 10;

    Point p2;
    // Deep copy
    deepCopyPoint(&p2, &p1);

    printf("p2.x = %d, p2.y = %d, *p2.ptr = %d\n", p2.x, p2.y, *(p2.ptr));

    // Modifying p1.ptr will not affect p2.ptr
    *(p1.ptr) = 20;
    printf("After modifying p1.ptr, *p2.ptr = %d\n", *(p2.ptr));

    free(p1.ptr);
    free(p2.ptr);
    return 0;
}
Output:
p2.x = 1, p2.y = 2, *p2.ptr = 10
After modifying p1.ptr, *p2.ptr = 10

The memory layout now look like this:

p1:  +------+------+------+
     |  x   |  y   | ptr  |
     +------+------+------+
     |  1   |  2   |  *---|----> 10 (in heap memory)
     +------+------+------+

p2:  +------+------+------+
     |  x   |  y   | ptr  |
     +------+------+------+
     |  1   |  2   |  *---|----> 10 (newly allocated
     +------+------+------+          in heap memory)
Example in C++:
#include <iostream>

class Point {
public:
    int x, y;
    int* ptr;

    Point(int xVal, int yVal, int ptrVal) : x(xVal), y(yVal) {
        ptr = new int(ptrVal);
    }

    // Deep copy constructor
    Point(const Point& p) : x(p.x), y(p.y) {
        ptr = new int(*p.ptr);
    }

    // Overloaded assignment operator for deep copy
    Point& operator=(const Point& p) {
        if (this != &p) { // Self-assignment check
            x = p.x;
            y = p.y;
            delete ptr;
            ptr = new int(*p.ptr);
        }
        return *this;
    }

    void display() const {
        std::cout << "x = " << x << ", y = " << y << ", *ptr = " << *ptr << std::endl;
    }

    ~Point() {
        delete ptr;
    }
};

int main() {
    Point p1(1, 2, 10);
    Point p2 = p1; // Deep copy using copy constructor

    p1.display();
    p2.display();

    *p1.ptr = 20;
    p1.display();
    p2.display(); // p2.ptr is not affected

    return 0;
}
Output:
x = 1, y = 2, *ptr = 10
x = 1, y = 2, *ptr = 10
x = 1, y = 2, *ptr = 20
x = 1, y = 2, *ptr = 10