Elementary Overview
Copy assignment is harder than it first appears because the destination object already owns a valid state before assignment begins. If copying the new state fails halfway through, the class must not leak resources or leave the destination corrupted. The copy-and-swap idiom solves this by preparing a complete temporary copy first, then committing the new state with a non-throwing swap.
This lesson continues directly from OSC++.024: Rule of Five and Rule of Zero, OSC++.023: Perfect Forwarding, OSC++.022: Move Semantics, and OSC++.021: Smart Pointers and Ownership. The focus is one operation: designing assignment so failure does not partially destroy a valid object.
What You Should Learn
- Why copy assignment has more failure paths than copy construction.
- What the basic, strong, and no-throw exception guarantees mean.
- How copy-and-swap turns assignment into prepare-then-commit.
- Why the copy step may throw while
swapshould normally benoexcept. - How copy-and-swap naturally handles self-assignment.
- Why pass-by-value assignment can combine copy and move assignment logic.
- When copy-and-swap is elegant but not necessarily the fastest implementation.
The Core Copy-and-Swap Pattern
A compact modern form takes the right-hand side by value. Constructing that parameter performs either a copy or a move before the function body commits anything to the destination. Inside the function, a non-throwing swap exchanges the destination state with the prepared temporary.
class Buffer {
public:
Buffer& operator=(Buffer other) {
swap(other);
return *this;
}
void swap(Buffer& other) noexcept {
using std::swap;
swap(data_, other.data_);
swap(size_, other.size_);
}
private:
std::unique_ptr<int[]> data_;
std::size_t size_{};
};
The temporary parameter other first acquires the incoming value. If constructing that temporary throws, the assignment function body never commits a partial change. If construction succeeds, swap exchanges the complete states. When other goes out of scope, its destructor releases the destination’s former resources.

Understand The Exception Guarantees
Exception safety is not the claim that a function can never fail. It describes what remains true after failure. Microsoft’s modern C++ guidance describes three common levels: the basic guarantee, the strong guarantee, and the no-throw guarantee. The basic guarantee keeps objects valid and prevents leaks. The strong guarantee behaves like a transaction: either the operation succeeds, or the observable state remains unchanged. The no-throw guarantee promises that the operation itself does not allow exceptions to escape.
Copy-and-swap is attractive because the potentially throwing work happens while creating the temporary. The existing destination object is not modified until the swap step. If swap is truly non-throwing, that final commit can provide the strong guarantee.
The Copy Step Can Throw
Do not mark the pass-by-value assignment operator noexcept merely because its internal swap is noexcept. Creating the by-value parameter may invoke a copy constructor, and copying may allocate memory or copy members whose constructors can throw. The important guarantee is that this failure happens before the destination is changed.
Buffer& operator=(Buffer other) {
// If copying into 'other' failed, execution never reached here.
swap(other); // should not throw
return *this;
}
The swap function, by contrast, should normally exchange members whose own swaps are non-throwing. Standard-library resource handles such as std::unique_ptr are especially useful here because their swaps are designed for inexpensive ownership exchange.
Why Self-Assignment Works Naturally
Traditional hand-written copy assignment often checks if (this == &rhs) because deleting the destination’s resource before copying from the source can destroy the very data being copied. Copy-and-swap does not depend on that fragile ordering. The temporary copy is complete before the destination’s old state is exchanged, so an expression such as a = a; remains valid without a special early-return branch.
Pass By Value Can Reuse Move Construction
The by-value parameter also gives the compiler a useful choice. When assignment receives an lvalue, constructing other uses the copy constructor. When it receives an rvalue, the parameter can be move-constructed instead. The same assignment body can therefore accept both copied and moved input states.
Buffer a;
Buffer b;
a = b; // parameter is copied from b
a = Buffer{1024}; // parameter can be moved from the temporary
This compact design is elegant, but it should not be mistaken for a universal performance rule. A specialized copy-assignment operator may be able to reuse an existing allocation rather than always creating a new temporary resource.
Copy-and-Swap Is About Correctness First
Copy-and-swap is often taught because it reduces duplicated cleanup logic and makes exception safety easier to reason about. It is not automatically faster than a carefully written assignment operator. If a destination already owns a large buffer with sufficient capacity, a specialized copy assignment may reuse that memory and avoid a fresh allocation. Howard Hinnant has specifically highlighted that copy-and-swap can impose measurable overhead in classes containing reusable resources such as vectors or strings.
The design decision is therefore contextual: use copy-and-swap when its strong safety, compactness, and maintainability are worth the temporary. Prefer Rule of Zero when standard-library members can manage resources automatically. Write specialized assignment only when the resource model and measurements justify the added complexity.
A More Explicit Strong-Guarantee Assignment
You can also preserve the prepare-then-commit idea without using the pass-by-value form for every assignment. One approach constructs replacement resources first, then swaps or installs them only after all throwing work succeeds.
Buffer& Buffer::operator=(const Buffer& rhs) {
if (this == &rhs) {
return *this;
}
Buffer replacement(rhs); // may throw; *this is unchanged
swap(replacement); // commit, ideally noexcept
return *this;
}
This form makes the strong guarantee explicit and lets copy assignment remain a distinct operation from move assignment if the class benefits from specialized behavior.
Design Checklist
- Prefer Rule of Zero when standard-library members already provide correct ownership.
- If custom assignment is required, identify every operation that may throw.
- Do not destroy the valid old state until replacement state is ready.
- Keep
swapcheap andnoexceptwhen the member types permit it. - Verify self-assignment remains safe.
- Verify the object remains valid after allocation or copy failure.
- Measure before assuming copy-and-swap is faster or slower.
- Document whether assignment offers the basic or strong exception guarantee.
Practical Exercise
- Create a class that owns a dynamically allocated array and write a correct copy constructor and destructor.
- Implement copy assignment manually by allocating replacement storage before releasing the old storage.
- Implement a second version using copy-and-swap.
- Test
a = a;for both implementations. - Force an allocation failure or use a member type whose copy constructor throws, then verify the destination object remains valid.
- Replace the raw allocation with
std::vectorand determine whether Rule of Zero eliminates the custom assignment code entirely. - Benchmark repeated assignments where the destination already has reusable capacity and compare specialized assignment with copy-and-swap.
Knowledge Check + Answers
- What is the main idea of copy-and-swap? Build a complete replacement first, then commit it with swap.
- What does the strong exception guarantee mean? If the operation fails, the observable state remains unchanged.
- Can constructing the temporary copy throw? Yes. That is acceptable because the destination has not yet been modified.
- Why should swap normally be noexcept? The commit step should not introduce a new failure after replacement state is ready.
- Does copy-and-swap require a manual self-assignment check? Usually no; preparing a separate temporary makes self-assignment naturally safe.
- Is copy-and-swap always the fastest assignment strategy? No. A specialized assignment operator may reuse existing storage and avoid temporary allocation.
- What design should usually be considered before custom copy-and-swap? Rule of Zero with RAII-aware standard-library types.
Reference Resources
- cppreference — Copy Assignment Operator
- cppreference — Rule of Three/Five/Zero
- Microsoft Learn — Modern C++ Exception Handling and Exception Safety
- Stack Overflow — What is the copy-and-swap idiom?
- OSC++.024 — Rule of Five and Rule of Zero
Elementary Review
Copy-and-swap treats assignment like a small transaction. Prepare a valid replacement first. If preparation fails, keep the old object. If preparation succeeds, commit with a non-throwing swap. This makes correctness and exception safety easier to reason about, while Rule of Zero remains the preferred destination whenever standard-library types can manage the resource for you.
Editor’s Note
The featured image is an original 1200×630 BitcoinVersus.Tech cover created specifically for OSC++.025 and is not reused in the body. The body uses a separate original 1200×675 English instructional diagram. The lesson contains three unique English-language YouTube videos implemented as responsive native Gutenberg 16:9 embed blocks and one directly relevant English-language social-media embed. Ordinary lesson prose is not placed inside bordered, shaded, card, callout, panel, or fixed-width text boxes.
BitcoinVersus.Tech content is provided for informational and educational purposes.

Leave a Reply