DeveloperBreeze

Deep Copy in C++: How to Avoid Shallow Copy Pitfalls

Overview

In C++, copying objects can lead to serious bugs if you're dealing with raw pointers. By default, C++ uses shallow copy, which means only the pointer's value is copied—not the data it points to.

This tutorial covers:

  • What shallow vs deep copy means
  • The problems caused by shallow copy
  • How to implement deep copy correctly
  • A practical class example with dynamic memory
  • When to use Rule of Three vs Rule of Five

What Is Shallow Copy?

A shallow copy copies the values of member variables as-is. If your class has a pointer member, both the original and copy point to the same memory.

class Shallow {
public:
    int* data;

    Shallow(int val) {
        data = new int(val);
    }

    ~Shallow() {
        delete data;
    }
};

Now consider:

Shallow a(10);
Shallow b = a;  // default copy constructor

This causes both a.data and b.data to point to the same memory. When both destructors run, delete is called twice on the same pointer — undefined behavior!


What Is Deep Copy?

A deep copy duplicates the actual data pointed to, not just the pointer.

class Deep {
public:
    int* data;

    Deep(int val) {
        data = new int(val);
    }

    // Copy constructor for deep copy
    Deep(const Deep& other) {
        data = new int(*other.data);
    }

    // Assignment operator for deep copy
    Deep& operator=(const Deep& other) {
        if (this != &other) {
            delete data;
            data = new int(*other.data);
        }
        return *this;
    }

    ~Deep() {
        delete data;
    }
};

Rule of Three

If your class handles dynamic memory:

  • Copy Constructor
  • Copy Assignment Operator
  • Destructor

You must implement all three. This is called the Rule of Three.


Example: Deep Copy for a String Wrapper

class String {
private:
    char* buffer;

public:
    String(const char* str) {
        buffer = new char[strlen(str) + 1];
        strcpy(buffer, str);
    }

    // Copy constructor
    String(const String& other) {
        buffer = new char[strlen(other.buffer) + 1];
        strcpy(buffer, other.buffer);
    }

    // Assignment operator
    String& operator=(const String& other) {
        if (this != &other) {
            delete[] buffer;
            buffer = new char[strlen(other.buffer) + 1];
            strcpy(buffer, other.buffer);
        }
        return *this;
    }

    ~String() {
        delete[] buffer;
    }

    void print() const {
        std::cout << buffer << std::endl;
    }
};

Usage

String a("Hello");
String b = a;       // deep copy
String c("World");
c = a;              // deep assignment

All objects manage their own memory independently.


Modern C++: Rule of Five

In C++11 and newer, also consider:

  • Move Constructor
  • Move Assignment Operator

This is the Rule of Five. Add move semantics if your class is performance-sensitive and uses resource ownership.


Conclusion

When your class uses raw pointers:

  • Avoid shallow copies.
  • Always implement deep copy logic.
  • Follow the Rule of Three (or Rule of Five).
  • Prefer std::string, std::vector, or smart pointers in modern C++.

Understanding deep copy is essential for writing robust, bug-free C++ code.

Related Posts

More content you might like

Tutorial

Avoiding Memory Leaks in C++ Without Smart Pointers

Not elegant. Easy to forget or misplace deletes. Let's go better.

RAII (Resource Acquisition Is Initialization) is a pattern where resource allocation is tied to object lifetime. When an object goes out of scope, its destructor cleans up.

Apr 11, 2025
Read More
Tutorial

Implementing a Domain-Specific Language (DSL) with LLVM and C++

#include "DSL/Lexer.h"
#include <cctype>
#include <cstdlib>

Lexer::Lexer(const std::string& input) : input(input) {}

char Lexer::currentChar() {
    if (pos < input.size()) {
        return input[pos];
    }
    return '\0';
}

void Lexer::advance() {
    pos++;
}

void Lexer::skipWhitespace() {
    while (std::isspace(currentChar())) {
        advance();
    }
}

Token Lexer::number() {
    size_t start = pos;
    while (std::isdigit(currentChar()) || currentChar() == '.') {
        advance();
    }
    std::string numStr = input.substr(start, pos - start);
    double value = std::strtod(numStr.c_str(), nullptr);
    return { TokenType::Number, numStr, value };
}

Token Lexer::getNextToken() {
    skipWhitespace();

    char current = currentChar();

    if (current == '\0') {
        return { TokenType::EndOfFile, "", 0 };
    }
    if (std::isdigit(current) || current == '.') {
        return number();
    }

    Token token;
    token.text = std::string(1, current);
    token.value = 0;
    switch (current) {
        case '+': token.type = TokenType::Plus; break;
        case '-': token.type = TokenType::Minus; break;
        case '*': token.type = TokenType::Asterisk; break;
        case '/': token.type = TokenType::Slash; break;
        case '(': token.type = TokenType::LParen; break;
        case ')': token.type = TokenType::RParen; break;
        default: token.type = TokenType::Invalid; break;
    }
    advance();
    return token;
}

We’ll implement a recursive-descent parser that constructs an Abstract Syntax Tree (AST) from tokens. Our grammar is defined with standard operator precedence:

Feb 12, 2025
Read More
Article
javascript

20 Useful Node.js tips to improve your Node.js development skills:

No preview available for this content.

Oct 24, 2024
Read More
Tutorial
javascript

Advanced JavaScript Tutorial for Experienced Developers

You can also use fetch to send data to a server by specifying the HTTP method and including a request body.

fetch('https://api.example.com/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({ name: 'John Doe', age: 30 })
})
    .then(response => response.json())
    .then(data => console.log('Success:', data))
    .catch(error => console.error('Fetch error:', error));

Sep 02, 2024
Read More

Discussion 0

Please sign in to join the discussion.

No comments yet. Be the first to share your thoughts!