Question bank — Copy constructor and copy assignment — Rule of Three
Prerequisite ideas live in the parent note, with deeper threads in Deep vs shallow copy, Self-assignment and exception safety, Destructors and RAII, and Rule of Zero and smart pointers.
The running class (context for every snippet)

The diagram above is the mental model behind every trap: the three special members are three arrows into and out of an object's owned buffer — birth-by-copy, rebirth-by-copy, and death.
True or false — justify
The compiler always generates a copy constructor for you.
If you write only a destructor, the class is still safe to copy.
delete[] twice on it; needing a destructor is the signal you need all three.MyString b = a; calls the copy assignment operator.
b is being born, so this is the copy constructor; the = here is initialization, not assignment.Copy assignment must return void since it only mutates the object.
T& (a reference to the same object, i.e. *this) so that chains like a = b = c work, mirroring how built-in = evaluates right-to-left and yields a value.The self-assignment guard is only needed for literal a = a; in code.
arr[i] = arr[j] with i == j, or two references/pointers naming the same object; the danger is runtime identity, not textual identity.A deep copy is always more correct than a shallow copy.
Rule of Zero means you never write any constructors.
std::vector/std::unique_ptr handle copy/move/destroy — see Rule of Zero and smart pointers.Copy-and-swap is slower and should be avoided.
delete[]/new); you pay that possible extra allocation in exchange for self-assignment and strong exception safety for free.Spot the error
MyString& operator=(const MyString& o) {
delete[] data;
data = new char[o.len + 1];
std::strcpy(data, o.data);
len = o.len; return *this;
}::: Missing self-assignment guard — on a = a, delete[] data frees the very buffer o.data points at, so the following strcpy reads freed memory (o.data == data); add if (this == &o) return *this; first.
MyString(const MyString& o) {
data = o.data;
len = o.len;
}::: Shallow copy in the copy constructor — it copies the pointer, not the bytes, so both objects own the same buffer; it must new char[o.len+1] then strcpy to make an independent buffer.
~MyString() { delete data; }::: Wrong delete form — the buffer came from new char[...], so releasing it needs delete[] data; mismatching new[] with scalar delete is undefined behavior — see Dynamic memory new and delete.
MyString& operator=(const MyString& o) {
if (this == &o) return *this;
len = o.len;
data = new char[len + 1];
std::strcpy(data, o.data);
return *this;
}::: Old buffer leaked — assignment overwrites data without delete[] data first, so the object's previous buffer is orphaned forever; free after the self-check, then allocate.
MyString& operator=(MyString other) { // by value
data = other.data;
len = other.len;
return *this;
}::: Broken copy-and-swap — plain assignment (data = other.data) leaks *this's old buffer and leaves other pointing at the same block, so other's destructor frees the buffer you just adopted; you must std::swap the members so other carries away the old guts to be destroyed.
MyString a("hi");
MyString b;
b = a;
// compiler-generated operator= (no user copy ops written)::: Shallow assignment double-frees — the default operator= copies the pointer, so a and b share one buffer; both destructors run delete[] on it, plus b's original buffer (if any) leaks.
Why questions
Why does "I needed to write one of the three" imply "I need all three"?
Why must operator= free the old resource but the copy constructor must not?
Why do we copy the contents rather than just the pointer for a uniquely-owned buffer?
Why is copy-and-swap automatically exception-safe?
*this is touched, and the swap itself must be noexcept (swapping a pointer and a size_t cannot throw); if new throws, it throws during the copy, *this is untouched, and since the swap can never fail once reached, the object either keeps its old state or fully adopts the new one — that all-or-nothing outcome is the strong guarantee, see Self-assignment and exception safety.Why must the swap in copy-and-swap be noexcept?
data and swapping len would leave the object half-updated (length disagreeing with buffer) — a broken invariant; because swapping raw pointers and integers cannot throw, the swap is a guaranteed-to-succeed commit step, which is exactly what the strong guarantee needs.Why does taking the parameter by value let copy-and-swap reuse the copy constructor?
Why does the Rule of Five extend the Rule of Three?
Why does RAII make the Rule of Zero possible?
Why return *this by reference and not by value from operator=?
(a = b) = c and chaining refer to the real object; returning by value would copy and break identity, and cost an extra allocation.Edge cases
What happens when you copy-assign an object to itself with a correct guard?
return *this;, leaving the object completely unchanged — the safe and intended no-op.What if the owned pointer is nullptr (an empty object) — is delete[] nullptr a problem?
delete[] on a null pointer is defined to do nothing, so an empty-state object destructs and copies safely without special-casing.What does deep-copying an empty string ("") allocate?
new char[len+1] with len == 0), so even the empty string owns a real 1-byte buffer that must still be delete[]-ed.If the copy constructor's new throws mid-copy, what state is the new object in — and how do we stay leak-safe?
MyString(const MyString& o) : data(nullptr), len(o.len) {
char* tmp = new char[len + 1]; // if this throws, no member points at it
std::strcpy(tmp, o.data); // succeeds → now commit
data = tmp; // member set only after allocation worked
}If new char[...] throws, tmp never existed as a leak (nothing owns it), data is still nullptr, and no partial buffer is orphaned.
Two objects a and b sharing one buffer via shallow copy — is the bug visible immediately?
What happens if you delete only the copy constructor but leave copy assignment defaulted?
If a class has a const member, why can the compiler still not give a working copy assignment?
const member cannot be reassigned, so the implicit copy assignment is deleted; you must either drop the const, redesign, or accept that objects are copy-constructible but not assignable.Recall One-line self-test
If you can state, for any of the five members, which life-event it handles and what the default gets wrong for a raw pointer, you have understood the Rule of Three, Five, and Zero as one idea.