5.2.8C++ Programming

Copy constructor and copy assignment — Rule of Three

2,001 words9 min readdifficulty · medium1 backlinks

WHAT are these three things?

The key distinction:

MyString a = b;   // copy CONSTRUCTOR (a is being born)
MyString c;
c = b;            // copy ASSIGNMENT  (c already exists)

WHY does the compiler-generated version break? (Shallow vs Deep)

Figure — Copy constructor and copy assignment — Rule of Three

Shallow copy aftermath:

a.data ──┐
         ├──► [ "hi\0" ]      one buffer, two owners
b.data ──┘

When a and b both destruct: delete[] (same ptr) runs twice → undefined behavior. Also, writing through a silently corrupts b (aliasing).


HOW to write them — deriving a safe MyString from scratch

We need a class that owns a char*. Let's build each member by reasoning about what must happen.

#include <cstring>   // strlen, strcpy
 
class MyString {
    char* data;      // owned resource
    std::size_t len;
 
public:
    // --- normal constructor ---
    MyString(const char* s = "") {
        len  = std::strlen(s);
        data = new char[len + 1];   // +1 for the '\0'
        std::strcpy(data, s);
    }

Why len+1? strings need a null terminator slot.

    // --- (1) Destructor: how the resource dies ---
    ~MyString() {
        delete[] data;              // [] because we did new[]
    }

Why delete[]? Array-new must be paired with array-delete, else UB.

    // --- (2) Copy constructor: born as a copy ---
    MyString(const MyString& other) {
        len  = other.len;
        data = new char[len + 1];   // OWN memory — deep copy
        std::strcpy(data, other.data);
    }

Why allocate fresh? So the new object owns an independent buffer; destroying one won't affect the other.

    // --- (3) Copy assignment: overwrite an existing object ---
    MyString& operator=(const MyString& other) {
        if (this == &other) return *this;   // guard self-assignment
        delete[] data;                      // free OLD resource
        len  = other.len;
        data = new char[len + 1];           // allocate new
        std::strcpy(data, other.data);
        return *this;                        // for chaining a=b=c
    }
};

WHY each line of operator= exists (steel-manned)


Worked examples


Modern footnote: Rule of Five & Rule of Zero


Active recall

What three members does the Rule of Three concern?
Destructor, copy constructor, and copy assignment operator.
Why does the Rule of Three exist?
If a class manages a resource manually, all three are needed together to avoid double-free, leaks, and aliasing bugs.
Distinguish copy constructor vs copy assignment by trigger.
Copy ctor: a new object is created from another. Copy assignment: an already-existing object is overwritten.
What is a shallow copy vs deep copy?
Shallow copies the pointer (shared buffer); deep allocates new memory and copies the contents.
Two bugs from default shallow copy of a pointer?
Double free on destruction, and aliasing (writing one corrupts the other).
Why must copy assignment guard against self-assignment?
Without it, freeing data before copying destroys the source you copy from (e.g. a = a).
Why must operator= free the old resource but the copy ctor must not?
The assigned-to object already owns a buffer (leak if not freed); the constructed object has none yet.
What signature is conventional for copy assignment?
T& operator=(const T& other) — returns reference for chaining.
What does copy-and-swap buy you?
Self-assignment safety and exception safety for free, by copying via value parameter then swapping.
Why pair new[] with delete[]?
Mismatched new/delete forms is undefined behavior.
What is the Rule of Zero?
Use RAII member types (string, vector, unique_ptr) so you write none of the special members.

Recall Feynman: explain to a 12-year-old

Imagine each object has a toy box (memory) and a note with the box's address. The lazy copy just photocopies the note, so now two kids point at the same box. When playtime ends, both kids try to throw the box away — but you can't throw away the same box twice, so everything breaks! The Rule of Three says: if you gave a kid a box, you must teach them three things — how to clean up the box, how to make their own copy of the box when born, and how to swap their box for a copy later. Do all three or none.


Connections

Concept Map

triggers need for

compiler gives

does

causes

leads to

requires

requires

requires

releases resource with delete[]

must perform

must perform

fixes

needs

prevents freeing before copy

Class owns a resource

Default copy is member-wise

Shallow copy of pointer

Two objects share one buffer

Double free and aliasing bugs

Rule of Three

Destructor ~T

Copy constructor T const T&

Copy assignment operator=

Deep copy allocates new memory

Self-assignment guard

Hinglish (regional understanding)

Intuition Hinglish mein samjho

Dekho, jab tumhare class ke andar koi raw pointer hota hai jo new se memory leta hai, tab problem shuru hoti hai. Agar tum copy constructor aur copy assignment khud nahi likhte, to compiler ek shallow copy banata hai — matlab sirf pointer ka address copy hota hai, andar ki memory nahi. Ab do objects ek hi heap buffer ko point karte hain. Jab dono destruct hote hain, dono delete[] chalate hain same pointer par → double free, program crash. Isi liye deep copy chahiye: nayi memory allocate karo aur content copy karo.

Rule of Three kehta hai: agar tumne in teeno me se ek bhi khud likha — destructor, copy constructor, ya copy assignment — to teeno likhne padenge. Kyunki teeno ek hi cheez describe karte hain: resource kaise paida, kaise copy, aur kaise destroy hota hai. Ek bhi chhoot gaya to bug pakka.

Sabse zyada galti operator= me hoti hai. Yaad rakho do baatein: (1) self-assignment guardif (this == &other) return *this;, warna a = a me pehle apni memory free kar doge aur phir usi se copy karoge (garbage). (2) purani memory free karo — assignment me object pehle se ek buffer rakhta hai, use delete[] karo warna memory leak. Copy constructor me ye free wala step nahi hota kyunki object abhi-abhi bana hai, uske paas purana buffer hota hi nahi.

Smart tarika: copy-and-swap idiom — parameter ko value se lo (copy automatically ban jayegi), phir std::swap se andar ka data badal do. Ye self-assignment aur exception dono ke liye safe hai. Aur modern C++ me best — Rule of Zero: std::string, std::vector, unique_ptr use karo, to tumhe in teeno me se kuch likhna hi nahi padega.

Go deeper — visual, from zero

Test yourself — C++ Programming

Connections