Skip to content
  • Copy Semantics: Shallow vs Deep · C++

Copy Semantics: Shallow vs Deep

LearnViaNotes @learnvianotes· 3 min read

Copying an object calls its copy constructor (new object) or copy assignment operator (existing object). If the class owns a raw resource, the default member-wise copy is shallow — and wrong.

Copy constructor vs copy assignment

C++
#include <iostream>
struct Tag {
    int v = 0;
    Tag() = default;
    Tag(const Tag& o) : v(o.v) { std::cout << "copy ctor\n"; }
    Tag& operator=(const Tag& o) { v = o.v; std::cout << "copy assign\n"; return *this; }
};
void byValue(Tag) {}
int main() {
    Tag a;
    Tag b = a;       // copy ctor: b is being created (the '=' here is not assignment)
    Tag c(a);        // copy ctor
    c = b;           // copy assignment: c already exists
    byValue(a);      // copy ctor for the parameter
}
Output
copy ctor
copy ctor
copy assign
copy ctor

The shallow-copy bug

C++ — double free (ASan)
#include <cstring>
class Buffer {
    char* data_;
public:
    explicit Buffer(const char* s) : data_(new char[std::strlen(s) + 1]) { std::strcpy(data_, s); }
    ~Buffer() { delete[] data_; }
    // no copy constructor → the compiler copies the *pointer*
};
int main() {
    Buffer a("hello");
    Buffer b = a;        // b.data_ == a.data_
}                        // both destructors delete[] the same block → double free

Deep copy done right

C++
#include <algorithm>
#include <cstring>
#include <iostream>
#include <utility>

class Buffer {
    std::size_t size_;
    char* data_;
public:
    explicit Buffer(const char* s) : size_(std::strlen(s)), data_(new char[size_ + 1]) {
        std::copy(s, s + size_ + 1, data_);
    }
    ~Buffer() { delete[] data_; }

    Buffer(const Buffer& o) : size_(o.size_), data_(new char[o.size_ + 1]) {   // deep copy
        std::copy(o.data_, o.data_ + size_ + 1, data_);
    }
    Buffer& operator=(Buffer o) {          // copy-and-swap: `o` is already a copy
        swap(*this, o);                    // take its contents; old ones die with `o`
        return *this;
    }
    friend void swap(Buffer& a, Buffer& b) noexcept {
        std::swap(a.size_, b.size_);
        std::swap(a.data_, b.data_);
    }
    void set(std::size_t i, char c) { data_[i] = c; }
    const char* str() const { return data_; }
};

int main() {
    Buffer a("hello");
    Buffer b = a;          // independent copy
    b.set(0, 'j');
    Buffer c("x");
    c = a;                 // assignment
    Buffer& alias = c;
    c = alias;             // self-assignment is safe
    std::cout << a.str() << ' ' << b.str() << ' ' << c.str() << '\n';
}
Output
hello jello hello

What's the difference between a shallow and a deep copy?

When is the copy constructor called?

Free for students and teachers

Write notes like this one

Cards for every idea, quizzes and flashcards, read aloud — on the web and your phone.

Something wrong with this note? Open it to report