LESSON 10 · BEGINNER4 min read

Copy Constructors, Deep vs Shallow Copying, and Copy Assignment

Analyze the hazards of shallow copying, implement deep-copy constructors, and master the exception-safe Copy-and-Swap idiom for copy assignment operators.

C++20 / C++23Free to readModule 3 of 11
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Diagnose double-free crashes and dangling pointers caused by compiler-generated shallow copy operations
  • Implement custom deep-copy constructors allocating independent heap buffers
  • Master the Copy-and-Swap idiom to implement strong exception-safe copy assignment operators (operator=)

01The Disaster of Compiler-Generated Shallow Copies

If a class manages raw heap resources (such as a dynamic array char* data_) and the developer does not write explicit copy operations, the C++ compiler automatically synthesizes a default copy constructor and copy assignment operator. The default implementation performs a Memberwise Shallow Copy.

In a shallow copy, pointer fields are copied bit-for-bit: new_obj.data_ = original.data_. Both objects now hold pointers to the exact same physical heap memory address! This leads directly to two catastrophic systems bugs:

1. Double Free: When the first object goes out of scope, its destructor deletes the heap memory. When the second object later goes out of scope, its destructor attempts to delete the already-freed memory address, triggering an immediate crash (SIGSEGV or heap corruption abort).

2. Dangling Pointer / Data Mutation: If one object modifies its buffer, it silently corrupts the data of the other object.

example.js⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02The Copy-and-Swap Idiom for Assignment Operators

Implementing a copy assignment operator (operator=) naively introduces severe hazards: self-assignment bugs (obj = obj deallocating its own memory before copying) and exception safety violations (if allocating the new buffer throws an exception, the original buffer is already deleted).

The Copy-and-Swap Idiom provides the gold standard solution by guaranteeing Strong Exception Safety. Instead of passing by reference, we pass the parameter by value: IntBuffer& operator=(IntBuffer other).

By taking other by value, the compiler's copy constructor automatically creates a deep copy. If that allocation throws, the original object remains untouched. Then, we swap the internal members using std::swap. When the operator returns, the temporary other goes out of scope, automatically deleting the old buffer in its destructor!

example.js⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03Self-Assignment and Exception Safety

Notice how Copy-and-Swap elegantly handles self-assignment (a = a). A temporary copy is created, swapped, and cleanly destroyed without requiring explicit if (this == &other) guard statements.

Furthermore, because the swap operation is noexcept, the assignment operator cannot throw an exception once the parameter copy has succeeded. This provides the Strong Exception Guarantee: the operation either succeeds completely, or leaves the original object unmodified.

04Shallow vs Deep Copy Comparison

Contrasting copy behaviors across memory allocation and pointer aliasing:

Whenever a class owns raw pointers or OS handles, developers must either implement custom deep copies or explicitly delete copy operations (= delete) to enforce unique ownership.

notes.txtPlain text
+-----------------------+-------------------------+-------------------------+| Feature               | Shallow Copy (Default)  | Deep Copy (Custom)      |+-----------------------+-------------------------+-------------------------+| Heap Memory Pointers  | Shared / Aliased        | Distinct / Independent  || Destructor Execution  | Triggers Double-Free!   | Safe independent delete || Mutation Isolation    | Corrupts peer object    | Completely isolated     || Memory Allocation     | 0 bytes (pointer copy)  | Full buffer allocated   || Recommended When      | Trivially copyable types| Managing raw resources  |+-----------------------+-------------------------+-------------------------+

05Production Systems Architecture & Hardware Trade-Offs

In industrial-grade systems development, resource management extends beyond heap memory deallocation. Critical systems must reliably manage operating system file descriptors, epoll event loops, GPU CUDA streams, and hardware crypto acceleration handles. Relying on manual cleanup routines inevitably introduces resource leaks along unexpected error propagation paths, ultimately causing file descriptor exhaustion and server unresponsiveness.

By adhering strictly to the RAII idiom, engineers ensure that resource lifecycles are bound deterministically to object scope. Whether a function exits normally, returns early due to a validation failure, or unwinds the stack following an unhandled exception, destructors execute in exact reverse order of construction, guaranteeing leak-free reclamation without garbage collection pauses.

LET IT CLICK

The ideas worth keeping.

  • Default shallow copying causes fatal double-free crashes when managing heap pointers.
  • Deep copying allocates fresh independent heap buffers and copies values element-by-element.
  • The Copy-and-Swap idiom guarantees strong exception safety for copy assignment operators.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Exercise 1: Identify the critical bug in this naive assig

Identify the critical bug in this naive assignment operator: Buffer& operator=(const Buffer& rhs) { delete[] data_; data_ = new int[rhs.size_]; return *this; }

your_turn_1.cpp⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.cppC++
// Bug 1: Self-assignment vulnerability (obj = obj).// delete[] data_ destroys the source buffer before rhs.size_ or rhs.data_ can be read!// Bug 2: Lack of exception safety.// If 'new int[rhs.size_]' throws std::bad_alloc, 'data_' is left pointing to deleted memory,// leaving the object in an invalid, corrupted state that crashes upon destruction.

What happens if an object is assigned to itself, or if operator new throws?

02

Exercise 2: Write a friend swap function for a class mana

Write a friend swap function for a class managing a char* str_ and size_t len_.

your_turn_2.cpp⚙️C++
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.cppC++
class MyString {    char* str_;    size_t len_;public:    friend void swap(MyString& a, MyString& b) noexcept {        using std::swap;        swap(a.str_, b.str_);        swap(a.len_, b.len_);    }};

Use std::swap on member variables inside a noexcept friend function.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01What causes a double-free crash when copying objects that manage raw heap pointers with default copy constructors?
02Why does the Copy-and-Swap idiom take its parameter by value (T other) in the assignment operator?
03How can a developer completely prevent an RAII class from being copied?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Exercise 1: Identify the critical bug in this naive assig

Identify the critical bug in this naive assignment operator: Buffer& operator=(const Buffer& rhs) { delete[] data_; data_ = new int[rhs.size_]; return *this; }

EXPECTED OUTPUT
Build completed. Binary executed.
[Exit code: 0]
main.cppC++
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.