Skip to content
C++ Better Explained
Go back
Shallow Copy vs Deep Copy in C++ (With Examples)

Shallow Copy vs Deep Copy in C++

Short answer: a shallow copy copies a pointer’s address, so the original and the copy share the same memory. A deep copy allocates new memory and copies the data, so each object owns its own. C++ copies pointers shallowly by default — harmless for plain values and standard containers, but a crash waiting to happen for memory you manage yourself with new and delete.

Shallow copyDeep copy
What gets copiedThe pointer (an address)The data the pointer points to
MemoryShared between both objectsEach object has its own
Change one object……and the other changes too…and the other is unaffected
SpeedFast — copies a few bytesSlower — copies all the data
C++ default for raw pointers?YesNo — you write it yourself
Main dangerDouble free, dangling pointersNone, if written correctly

The Picture to Keep in Your Head

A pointer is like a house address written on a slip of paper.

The trouble starts at demolition time. With a shallow copy, both slip-holders think they own the house — and both try to knock it down.

A Shallow Copy in Action

Here’s a class that keeps an array of scores on the heap. It has no copy constructor, so C++ generates one that copies each member as-is — including the pointer:

#include <iostream>

class Scoreboard {
public:
    int* scores;   // points to an array on the heap
    int size;

    Scoreboard(int n) : scores(new int[n]{}), size(n) {}
};

int main() {
    Scoreboard a(3);
    Scoreboard b = a;   // default copy: b.scores gets a's ADDRESS

    b.scores[0] = 99;   // change b...

    std::cout << a.scores[0] << '\n';            // ...and a changes too: 99
    std::cout << (a.scores == b.scores) << '\n'; // 1 (same address)
}

Output:

99
1

We only changed b, but a changed too, because there is only one array. (This version also never frees its array. Fixing that is where the real trouble starts.)

Why a Shallow Copy Crashes

Add the destructor the class obviously needs, and copying it becomes a time bomb:

#include <iostream>

class Scoreboard {
public:
    int* scores;
    int size;

    Scoreboard(int n) : scores(new int[n]{}), size(n) {}
    ~Scoreboard() { delete[] scores; }   // free the array
};

int main() {
    Scoreboard a(3);
    Scoreboard b = a;   // shallow copy
    std::cout << "End of main\n";
}   // b's destructor frees the array, then a's frees it AGAIN

On Linux with GCC:

End of main
free(): double free detected in tcache 2
Aborted

Freeing the same memory twice is undefined behaviour. Here the program aborted; on another system it might crash elsewhere, corrupt data silently, or appear to work until one day it doesn’t. A shallow copy also causes dangling pointers: if a is destroyed first, b.scores points to memory that no longer belongs to the program.

Pointers and memory still fuzzy? The C++ Better Explained Ebook explains pointers, memory and OOP with pictures you can keep in your head, across 87 plain-English pages. Just $19.

A Deep Copy in Action

The fix is to take control of copying: allocate new memory and copy the values across. Because the class manages memory, it needs a copy constructor, a copy assignment operator and a destructor — the rule of three:

#include <algorithm>
#include <iostream>

class Scoreboard {
public:
    int* scores;
    int size;

    Scoreboard(int n) : scores(new int[n]{}), size(n) {}

    // Copy constructor: allocate NEW memory, then copy the values
    Scoreboard(const Scoreboard& other)
        : scores(new int[other.size]), size(other.size) {
        std::copy(other.scores, other.scores + size, scores);
    }

    // Copy assignment: same idea for a = b on an existing object
    Scoreboard& operator=(const Scoreboard& other) {
        if (this != &other) {
            int* fresh = new int[other.size];
            std::copy(other.scores, other.scores + other.size, fresh);
            delete[] scores;
            scores = fresh;
            size = other.size;
        }
        return *this;
    }

    ~Scoreboard() { delete[] scores; }
};

int main() {
    Scoreboard a(3);
    Scoreboard b = a;   // deep copy

    b.scores[0] = 99;

    std::cout << a.scores[0] << '\n';            // 0: a is untouched
    std::cout << (a.scores == b.scores) << '\n'; // 0 (different addresses)
}

Output:

0
0

Each object now owns its own array, so each destructor frees its own memory exactly once. For a line-by-line walkthrough of writing copy constructors, see the C++ copy constructor.

The Modern Way: Let std::vector Do the Deep Copy

You rarely need to write all of that. Standard containers already know how to deep copy themselves, so if you store the data in a std::vector, the compiler-generated copy is automatically a deep one:

#include <iostream>
#include <vector>

class Scoreboard {
public:
    std::vector<int> scores;

    Scoreboard(int n) : scores(n) {}
    // No copy constructor, assignment or destructor needed
};

int main() {
    Scoreboard a(3);
    Scoreboard b = a;   // std::vector copies its elements: a deep copy

    b.scores[0] = 99;

    std::cout << a.scores[0] << '\n';   // 0
    std::cout << b.scores[0] << '\n';   // 99
}

Same behaviour, a third of the code, and no way to get it wrong. This is the rule of zero: build classes out of types that manage themselves, and you don’t write any of the copying functions at all.

Deep or Shallow? A Cheat Sheet for Common Types

The default copy of a class copies each member with that member’s copy. So whether your class copies deeply depends on what’s inside it:

Member typeCopying it is…Notes
int, double, char, boolDeepJust values — nothing shared
std::stringDeepCopies the characters
std::vector<int>, std::array, std::mapDeepCopies every element
Raw pointer int*ShallowCopies only the address
std::vector<int*>Shallow for the pointed-to valuesCopies the pointers, not what they point to
std::shared_ptrShallow, on purposeShared ownership, freed once — safe
std::unique_ptrCan’t be copiedCompile error — you must choose

Two of these deserve a closer look.

A vector of pointers copies the pointers. The vector itself is deep-copied, but its elements are addresses, so the copies point at the same objects:

#include <iostream>
#include <vector>

int main() {
    int x = 1, y = 2;

    std::vector<int*> original = {&x, &y};
    std::vector<int*> copy = original;   // copies the pointers, not x and y

    *copy[0] = 100;

    std::cout << x << '\n';            // 100: both vectors point at the same x
    std::cout << *original[0] << '\n'; // 100
}

shared_ptr is a shallow copy you can trust. Copying a shared_ptr shares the object deliberately, and it keeps count of the owners so the memory is freed exactly once:

#include <iostream>
#include <memory>

int main() {
    auto a = std::make_shared<int>(5);
    auto b = a;   // shallow on purpose: both share one int

    *b = 42;

    std::cout << *a << '\n';           // 42
    std::cout << a.use_count() << '\n'; // 2 owners; freed once, when both are gone
}

unique_ptr refuses to be copied. A class with a unique_ptr member can’t be copied at all — GCC reports use of deleted function 'Profile::Profile(const Profile&)'. That’s a feature: it forces you to decide. If you want a deep copy, write it with std::make_unique:

#include <iostream>
#include <memory>

struct Profile {
    std::unique_ptr<int> level = std::make_unique<int>(1);

    Profile() = default;

    // Deep copy: make a new int holding the same value
    Profile(const Profile& other)
        : level(std::make_unique<int>(*other.level)) {}
};

int main() {
    Profile a;
    Profile b = a;

    *b.level = 5;

    std::cout << *a.level << ' ' << *b.level << '\n';   // 1 5
}

Where Move Fits In

There’s a third option besides shallow and deep copy: a move. Moving takes the pointer from the original and leaves the original empty, so only one object owns the memory. It’s as cheap as a shallow copy and as safe as a deep one — which is why C++ moves objects automatically when the original is about to disappear anyway. See move semantics for how it works.

Shallow copyDeep copyMove
CostCheapExpensiveCheap
Original afterwardsShares memory with the copyUnchangedEmpty
Safe with owned memory?NoYesYes

The Interview Answer

If you’re asked “what’s the difference between a shallow copy and a deep copy?”:

A shallow copy copies the pointer, so both objects share the same memory; a deep copy allocates new memory and copies the data, so each object is independent. C++‘s default copy is shallow for raw pointers, which leads to double frees, so a class that owns raw memory needs a deep-copying copy constructor, copy assignment operator and destructor — the rule of three. In modern C++ you avoid that by using std::vector, std::string or smart pointers.


Take Your C++ Further

Shallow and deep copies only make sense once pointers and memory click. The C++ Better Explained Ebook explains pointers, memory and OOP in plain English, with pictures you can keep in your head. Just $19.

👉 Get the C++ Better Explained Ebook — $19


📋

Free Download: The 10 Mistakes Every C++ Beginner Makes

A free 1-page checklist that shows the exact traps that slow down every C++ beginner — so you can avoid them from day one.

🔒 No spam. Unsubscribe anytime.


Share this post on:

Written by

Sahil Bora

Software Engineer. Author and creator of C++ Better Explained.


Previous Post
C++ Programs for Beginners: 40+ Examples With Full Code
Next Post
How to Fix 'was not declared in this scope' in C++

Keep Learning