Rule of Zero / Three / Five
A class that owns a resource must define destructor, copy, and move operations as a matched set, not piecemeal — this topic explains why they travel together and how modern C++ lets most classes define none of them at all.
Every C++ class has up to six special member functions that the compiler can generate for you: default constructor, destructor, copy constructor, copy assignment operator, move constructor, and move assignment operator. Most of the time the generated versions are exactly right. But the moment a class takes on the job of managing a resource by hand — memory, a file handle, a socket, a lock — the generated destructor, copy, and move operations stop being correct, and the class has to define its own. The Rule of Three, Rule of Five, and Rule of Zero are three closely related guidelines that describe when and how to do this.
The Special Member Functions
This topic is concerned with five of the special member functions: the destructor, copy constructor, copy assignment operator, move constructor, and move assignment operator. Together they answer one question for a given object: what happens when it is destroyed, copied, or moved.
Point declares none of the five special members explicitly.
Because Point declares none of them, the compiler is free to generate all five automatically, and for a simple aggregate like this the generated versions are correct: destruction does nothing special, and copy or move just copies or moves each member.
struct Point
{
int x;
int y;
};How the Compiler-Generated Defaults Behave
When the compiler generates a copy constructor, copy assignment operator, move constructor, or move assignment operator, it does so member-wise: each member of the class is copied or moved using that member's own copy or move operation. The generated destructor calls the destructor of each member in reverse declaration order.
b becomes an independent Point with the same x and y as a.
Member-wise behavior is exactly right as long as every member already manages its own resources correctly — which is the situation the Rule of Zero (covered later) tries to keep you in.
Point a{1, 2};
Point b = a; // member-wise copy: b.x = a.x, b.y = a.yThe Problem: A Class That Owns a Resource
Trouble starts when a class stores a raw handle to a resource and takes on responsibility for releasing it — a classic example is a class wrapping a dynamically allocated array.
Buffer allocates an int array in its constructor and releases it in its destructor — a hand-managed resource.
Buffer declares a destructor, but nothing else. The compiler still generates a copy constructor and copy assignment operator, and both simply copy the data pointer member-wise — not the array it points to.
After the shallow copy, a and b both believe they own the same heap array.
When a and b are destroyed, each destructor calls delete[] on the same pointer — the array is deleted twice, which is undefined behavior. Before that, any write through one object's data is silently visible through the other, because they were never independent objects to begin with.
class Buffer
{
public:
Buffer(std::size_t n)
: data(new int[n]), size(n)
{
}
~Buffer()
{
delete[] data;
}
private:
int* data;
std::size_t size;
};Buffer a(3);
Buffer b = a; // compiler-generated copy: shallow
a.data ---> +----+----+----+
| .. | .. | .. | (heap array, 3 ints)
+----+----+----+
^
b.data ------------+
Both a and b now store the SAME pointer value.Why Hand-Managing a Resource Creates Special-Member Complexity
The root cause is a mismatch: Buffer's destructor was written because the class needed non-default, resource-aware behavior at the end of an object's life. But copying and moving are just as much lifecycle operations as destruction is — a resource-owning class needs equally resource-aware behavior for all three, not only for destruction. Writing one without the other two leaves the class internally inconsistent: its destructor assumes exclusive ownership of the resource, while its copy operations silently create duplicate, non-owning aliases of it.
This observation is exactly what the Rule of Three formalizes.
The Rule of Three
The Rule of Three states: if a class needs a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly needs all three.
The reasoning runs in both directions. A user-defined destructor is usually a sign that the class manages a resource, which means the compiler-generated copy operations are almost certainly wrong for it (as Buffer demonstrated above). Conversely, if a class needs a custom copy constructor or copy assignment operator — typically because it needs a deep copy rather than a member-wise one — it is very likely managing a resource, and the compiler-generated destructor (which does nothing beyond destroying members) is unlikely to release that resource correctly either.
Fixing the Copy Constructor
Buffer needs a copy constructor that allocates its own array and copies the elements, rather than copying the pointer.
A deep copy: a new array is allocated for the new object, and elements are copied individually so the two objects never share storage.
The mechanics of copy constructors — copy initialization, member-wise copying, shallow vs. deep copy, self-assignment, and when copy operations are generated or suppressed — are covered in depth on the dedicated Copy Semantics page. This topic only needs enough of that picture to explain why copy belongs alongside the destructor.
Buffer(const Buffer& other)
: data(new int[other.size]), size(other.size)
{
for (std::size_t i = 0; i < size; ++i)
{
data[i] = other.data[i];
}
}Fixing the Copy Assignment Operator
Copy assignment has the extra responsibility of releasing whatever resource the target object already owned before it acquires a copy of the source's resource, and it must remain correct for self-assignment (x = x;).
The new array is built before the old one is released, so a failed allocation leaves the original object unchanged rather than half-destroyed.
This shape — build the replacement first, then release the old resource, then adopt the replacement — is one common way to keep copy assignment correct under self-assignment and exceptions. A more general technique for the same goal, copy-and-swap, is only mentioned here; its full design is not part of this topic.
Buffer& operator=(const Buffer& other)
{
if (this != &other)
{
int* newData = new int[other.size];
for (std::size_t i = 0; i < other.size; ++i)
{
newData[i] = other.data[i];
}
delete[] data;
data = newData;
size = other.size;
}
return *this;
}The Rule of Three: Complete Example
With all three operations defined together, a and b own separate arrays and modifying one never affects the other.
Buffer a(3) allocates a 3-element array and zero-initializes it a.set(0, 42) writes 42 into a's array
Buffer b = a invokes the copy constructor: a new, independent array is allocated for b and its contents copied b.set(0, 99) writes 99 into b's array only
#include <iostream>
class Buffer
{
public:
explicit Buffer(std::size_t n)
: data(new int[n]), size(n)
{
for (std::size_t i = 0; i < size; ++i)
{
data[i] = 0;
}
}
~Buffer()
{
delete[] data;
}
Buffer(const Buffer& other)
: data(new int[other.size]), size(other.size)
{
for (std::size_t i = 0; i < size; ++i)
{
data[i] = other.data[i];
}
}
Buffer& operator=(const Buffer& other)
{
if (this != &other)
{
int* newData = new int[other.size];
for (std::size_t i = 0; i < other.size; ++i)
{
newData[i] = other.data[i];
}
delete[] data;
data = newData;
size = other.size;
}
return *this;
}
void set(std::size_t index, int value)
{
data[index] = value;
}
int get(std::size_t index) const
{
return data[index];
}
private:
int* data;
std::size_t size;
};
int main()
{
Buffer a(3);
a.set(0, 42);
Buffer b = a; // deep copy: independent array
b.set(0, 99);
…Move Semantics Arrives: The Rule of Five
C++11 added two more special members: the move constructor and move assignment operator. Once they exist as a language feature, a resource-owning class that defines only the Rule of Three still compiles and behaves correctly, but it misses an opportunity — every place a Buffer is initialized or assigned from a temporary (an rvalue) falls back to the expensive deep copy, even though the temporary's resource could simply have been transferred instead of duplicated.
The Rule of Five extends the Rule of Three: if a class needs a user-defined destructor, copy constructor, or copy assignment operator, it should usually also define a move constructor and move assignment operator, so that moves are as cheap as the resource allows.
Implementing the Move Constructor
A move constructor steals the source object's resource instead of duplicating it, then leaves the source in a valid but empty state.
The pointer is copied (cheap), then the source is nulled out so its destructor will not free the array the new object now owns.
Rvalue references (Buffer&&), std::move, and the general mechanics of move construction and move assignment are the subject of the dedicated Move Semantics page. Here the point is narrower: a resource-owning class that defines copy and destruction should define these too, as part of the same coherent set.
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size)
{
other.data = nullptr;
other.size = 0;
}Implementing the Move Assignment Operator
The target's existing resource is released, the source's resource is taken over, and the source is left empty.
Move constructors and move assignment operators are conventionally marked noexcept when they cannot throw. This is not just documentation — standard containers such as std::vector rely on it to decide whether they may move elements during reallocation or must fall back to copying to preserve exception guarantees.
Buffer& operator=(Buffer&& other) noexcept
{
if (this != &other)
{
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}Why Move Operations Do Not Replace Copy
Adding move operations does not remove the need for copy operations — both are usually kept, and overload resolution chooses between them automatically based on whether the source expression is an lvalue or an rvalue.
The same initialization syntax selects copy or move depending on the value category of the source expression.
Buffer a(3);
Buffer b = a; // a is an lvalue -> copy constructor
Buffer c = std::move(a); // a is cast to an rvalue -> move constructor
Buffer d = Buffer(5); // a temporary is an rvalue -> move (or elided)The Rule of Five: Complete Example
b takes over a's 10-element array by moving; a is left in a valid, empty state.
Buffer a(10) allocates a 10-element array Buffer b = std::move(a) casts a to an rvalue reference, selecting the move constructor the move constructor transfers a.data into b and sets a.data to nullptr, a.size to 0
#include <iostream>
#include <utility>
class Buffer
{
public:
explicit Buffer(std::size_t n)
: data(new int[n]), size(n)
{
}
~Buffer()
{
delete[] data;
}
Buffer(const Buffer& other)
: data(new int[other.size]), size(other.size)
{
for (std::size_t i = 0; i < size; ++i)
{
data[i] = other.data[i];
}
}
Buffer& operator=(const Buffer& other)
{
if (this != &other)
{
int* newData = new int[other.size];
for (std::size_t i = 0; i < other.size; ++i)
{
newData[i] = other.data[i];
}
delete[] data;
data = newData;
size = other.size;
}
return *this;
}
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size)
{
other.data = nullptr;
other.size = 0;
}
Buffer& operator=(Buffer&& other) noexcept
{
if (this != &other)
{
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
ret
…Implicit Generation and Suppression Rules
Declaring any one of the five special members changes which of the others the compiler is still willing to generate for you. This is exactly why the Rule of Three/Five exist as guidelines rather than being enforced by the language: the compiler will happily let you declare only a destructor and keep silently generating a broken copy constructor unless you tell it otherwise.
Declaring a destructor still lets copy operations be generated, but that generation is a deprecated compatibility behavior — you should not rely on it. Declaring either move operation suppresses (deletes) both compiler-generated copy operations, since a class that defines move usually manages a resource that member-wise copying would handle incorrectly.
The practical consequence is the Rule of Three/Five itself: once you write one of these members by hand, do not leave the rest to accidental, possibly-wrong, or silently-deleted defaults — decide what each of the five should do and either write it or state your intent explicitly.
If you declare... | Copy ops generated? | Move ops generated?
--------------------------------+---------------------------+---------------------
Destructor | Yes (deprecated) | No
Copy constructor | Copy assignment: yes | No
Copy assignment operator | Copy constructor: yes | No
Move constructor | No (deleted) | Move assignment: no
Move assignment operator | No (deleted) | Move constructor: no
None of the five | Yes | YesThe Rule of Zero
The Rule of Zero is the modern default and, where it applies, the simplest of the three rules: design a class so that it declares none of the five special members at all, by making every member responsible for managing its own resource.
If a class's data members are all types like std::string, std::vector, std::unique_ptr, or other Buffer-style RAII types, then the compiler-generated destructor, copy operations, and move operations are already correct — each one simply invokes the corresponding operation on every member, and every member already knows how to destroy, copy, or move its own resource correctly.
The RAII Connection
RAII (Resource Acquisition Is Initialization) is the idiom of binding a resource's lifetime to an object's lifetime: acquire the resource in the constructor, release it in the destructor. Buffer, std::vector, std::string, and std::unique_ptr are all RAII types.
The Rule of Zero is what happens when RAII types are composed: a class built entirely out of RAII members does not need to write its own destructor, copy, or move logic, because composition already handles it — each member's own RAII behavior fires automatically as part of the containing object's own construction, copying, moving, and destruction.
Replacing a raw owning pointer with an RAII member (std::vector) moves resource management out of Widget entirely.
class Widget class Widget
{ {
int* data; std::vector<int> data;
// + destructor // no destructor needed
// + copy ctor // no copy ctor needed
// + copy assign // no copy assign needed
// + move ctor // no move ctor needed
// + move assign // no move assign needed
}; };
Rule of Five: 5 hand-written Rule of Zero: 0 hand-written
special members special membersRule of Zero in Practice: Refactoring Buffer
Buffer's entire reason for needing the Rule of Five was that it managed a raw int* by hand. Delegating that responsibility to std::vector<int>, which is itself a correctly-written RAII type, removes the need for any of the five special members.
No destructor, copy constructor, copy assignment, move constructor, or move assignment operator is declared — all five are compiler-generated, and all five are correct, because std::vector already manages its own array correctly.
This version is shorter, cannot leak or double-free, and its generated move operations are just as cheap as the hand-written ones from the Rule of Five version, because std::vector's own move constructor already just transfers its internal pointer.
#include <vector>
class Buffer
{
public:
explicit Buffer(std::size_t n)
: data(n, 0)
{
}
void set(std::size_t index, int value) { data[index] = value; }
int get(std::size_t index) const { return data[index]; }
private:
std::vector<int> data;
};When to Explicitly Default a Special Member
Sometimes a class needs one special member written by hand — for example, a destructor that logs something — while the rest would be perfectly correct as compiler-generated. Rather than leaving the others to be silently and possibly incorrectly suppressed, state the intent explicitly with = default.
The destructor is user-defined for logging, but explicitly defaulting the other four documents that they are still meant to behave normally — and restores move operations, which the user-defined destructor would otherwise suppress.
class LoggingBuffer
{
public:
explicit LoggingBuffer(std::size_t n) : data(n, 0) {}
~LoggingBuffer()
{
std::cout << "Buffer destroyed\n";
}
LoggingBuffer(const LoggingBuffer&) = default;
LoggingBuffer& operator=(const LoggingBuffer&) = default;
LoggingBuffer(LoggingBuffer&&) = default;
LoggingBuffer& operator=(LoggingBuffer&&) = default;
private:
std::vector<int> data;
};When to Explicitly Delete a Special Member
Some classes should not be copied at all — a class wrapping a unique operating-system handle, a mutex, or a scoped lock has no sensible copy semantics. Deleting the copy operations explicitly makes accidental copies a compile error instead of a subtle runtime bug.
Copying is deleted (a file handle cannot be sensibly duplicated), while moving is still supported so ownership can be transferred.
class FileHandle
{
public:
explicit FileHandle(const char* path);
~FileHandle();
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
FileHandle(FileHandle&&) noexcept;
FileHandle& operator=(FileHandle&&) noexcept;
private:
int descriptor;
};Non-Copyable, Movable-Only Types
The FileHandle pattern above — delete copy, keep move — is common enough to have a name: a movable-only type. It expresses unique ownership of a resource: at any moment exactly one object owns the handle, ownership can be transferred (moved) to another object, but it can never be duplicated (copied). std::unique_ptr is the standard library's own example of this pattern.
Attempting to copy a movable-only type is caught at compile time rather than causing a resource-management bug at runtime.
FileHandle a("log.txt");
FileHandle b = std::move(a); // OK: ownership transferred to b
// FileHandle c = a; // error: copy constructor is deletedA Boundary Note: Special Members and Base Classes
A class intended to be used polymorphically through a base-class pointer needs one more consideration on top of the Rule of Three/Five/Zero: its destructor should generally be virtual, so that deleting a derived object through a base pointer runs the correct derived destructor. That concern belongs to class hierarchies specifically and is covered on the Inheritance and Polymorphism pages; it is mentioned here only so the two topics are not confused with each other.
Comparison Table: Rule of Zero vs. Three vs. Five
Rule of Zero is the preferred default whenever it is achievable; Rule of Three/Five are what a class needs when it is the one directly doing the resource management that an RAII member would otherwise have done for it.
Rule | When it applies | What you declare | Typical class shape
--------+------------------------------------------+---------------------------------------------+---------------------------------
Zero | Members are all RAII types | None of the five special members | Composition of std::string,
| (no resource handled directly) | (all compiler-generated) | std::vector, std::unique_ptr...
--------+------------------------------------------+---------------------------------------------+---------------------------------
Three | Class owns a resource directly, | Destructor, copy constructor, | Pre-C++11-style resource wrapper,
| pre-move-semantics or move not needed | copy assignment operator | or move offers no real benefit
--------+------------------------------------------+---------------------------------------------+---------------------------------
Five | Class owns a resource directly and | Destructor, copy constructor, | Hand-written RAII wrapper around
| should sup
…Best Practices
Prefer the Rule of Zero: build classes out of existing RAII members (std::string, std::vector, std::unique_ptr, std::shared_ptr, other well-formed RAII classes) rather than raw owning handles, so no special member needs to be written by hand.
When a class genuinely must own a resource directly, implement the destructor, copy constructor, and copy assignment operator together as one unit, and add the move constructor and move assignment operator alongside them rather than leaving moves to fall back on expensive copies.
State intent explicitly with = default or = delete rather than relying on the compiler's suppression rules, which differ subtly depending on which member you declared first — explicit declarations are self-documenting and protect the class if those rules are misremembered.
Mark move constructors and move assignment operators noexcept whenever they truly cannot throw, since standard containers use that guarantee to decide whether they may move elements safely.
Guard copy assignment and move assignment against self-assignment, and prefer building the replacement resource before releasing the old one, so a failure during construction leaves the original object unchanged.
Keep resource ownership concentrated in as few classes as possible — a single well-tested RAII wrapper around a raw resource, reused everywhere by value, is easier to get right than the same raw handle scattered across many Rule-of-Five classes.
Complete Example: Rule of Five and Rule of Zero Side by Side
Both classes behave identically from the caller's point of view after a move, but VectorBuffer needed zero hand-written special members to get there.
RawBuffer r2 = std::move(r1) invokes the hand-written move constructor
VectorBuffer v2 = std::move(v1) invokes the compiler-generated move constructor, which moves the std::vector member both r1 and v1 end up empty (length 0); both r2 and v2 end up with length 4
#include <iostream>
#include <vector>
#include <utility>
// Rule of Five: hand-managed resource
class RawBuffer
{
public:
explicit RawBuffer(std::size_t n) : data(new int[n]{}), size(n) {}
~RawBuffer() { delete[] data; }
RawBuffer(const RawBuffer& other)
: data(new int[other.size]), size(other.size)
{
for (std::size_t i = 0; i < size; ++i) data[i] = other.data[i];
}
RawBuffer& operator=(const RawBuffer& other)
{
if (this != &other)
{
int* newData = new int[other.size];
for (std::size_t i = 0; i < other.size; ++i) newData[i] = other.data[i];
delete[] data;
data = newData;
size = other.size;
}
return *this;
}
RawBuffer(RawBuffer&& other) noexcept : data(other.data), size(other.size)
{
other.data = nullptr;
other.size = 0;
}
RawBuffer& operator=(RawBuffer&& other) noexcept
{
if (this != &other)
{
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
std:
…Mental Model
Think of the five special members as one coherent toolset for a single question, not five unrelated functions — you either need the whole toolset, or (better) none of it.
A resource-owning class needs a matched TOOLSET
for "what happens at end of life":
destructor copy ctor copy assign move ctor move assign
| | | | |
+------------+------------+-------------+------------+
|
all five describe the SAME
underlying question: who owns
this resource, and what happens
to it on destroy / copy / move?
If a member already answers that question for you (RAII types)
-> you need NONE of the five (Rule of Zero)
If you own the resource directly and only need correctness
-> you need the destructor + both copy ops (Rule of Three)
If you own the resource directly and want cheap transfers too
-> you need all five (Rule of Five)Quick Reference
A compact summary of when each rule applies and how to state intent explicitly.
Situation | Guideline
----------------------------------------------+---------------------------------------------
Class owns no resource directly | Rule of Zero: declare none of the five
Class owns a resource, no move benefit needed | Rule of Three: destructor + copy ctor/assign
Class owns a resource, cheap transfer wanted | Rule of Five: add move ctor/assign too
One member needs custom logic, rest are fine | = default the rest explicitly
Copying makes no sense for this type | = delete copy ctor/assign, keep move
Declaring a destructor | Suppresses implicit move generation
Declaring a move constructor or move assignment | Deletes implicit copy generationKey Takeaways
The destructor, copy constructor, copy assignment operator, move constructor, and move assignment operator describe one coherent question — resource ownership at end of life — not five independent functions.
The Rule of Three: a user-defined destructor, copy constructor, or copy assignment operator almost always means all three are needed.
The Rule of Five extends the Rule of Three with a move constructor and move assignment operator, so resource transfer can be cheap instead of falling back to a deep copy.
The Rule of Zero is the preferred modern default: compose classes out of existing RAII members so that none of the five special members need to be written by hand.
Declaring one special member changes which of the others the compiler will still generate — state intent explicitly with = default or = delete rather than relying on those suppression rules.
Mark move operations noexcept when they cannot throw, and make copy assignment safe under self-assignment.
A class meant to be used polymorphically through a base pointer has an additional destructor consideration covered separately on the Inheritance and Polymorphism pages.
A class declares only a destructor that calls delete[] on a raw member pointer, and declares nothing else. What is wrong with the class as written, and which rule describes the fix?
The compiler still generates a copy constructor and copy assignment operator, and both copy the pointer member-wise rather than the array it points to, so two objects end up sharing one array. Destroying both then double-frees it. The Rule of Three says a user-defined destructor almost always requires user-defined copy constructor and copy assignment operator as well, implementing a deep copy.
Why does the Rule of Five add move operations on top of the Rule of Three rather than replacing copy operations with move operations?
Copy and move serve different situations: copy is needed whenever an independent duplicate is required (the source must remain usable afterward), while move is only valid when the source can be left empty. Overload resolution selects move automatically for rvalue sources and copy for lvalue sources, so both need to exist side by side for the class to behave correctly in either situation.
Explain why a class composed only of std::string and std::vector members typically needs zero hand-written special members, using the term RAII in your answer.
std::string and std::vector are themselves RAII types: each already manages its own resource correctly through its own constructor, destructor, copy, and move operations. The compiler-generated special members of the containing class simply invoke each member's own correct operation, so the containing class inherits correct behavior for free — this is the Rule of Zero.
A class's copy constructor and copy assignment operator are hand-written for a deep copy, but no move constructor or move assignment operator is declared. What happens when the class is initialized from a temporary of the same type, and why?
Because no move constructor is declared, initializing from a temporary (an rvalue) still binds to the copy constructor's const-reference parameter and performs a full deep copy, rather than the cheaper transfer a move constructor could have performed. The class is still correct, but it misses the efficiency the Rule of Five would have provided.
What does declaring a move constructor do to a class's compiler-generated copy constructor and copy assignment operator, and why does this make sense for a resource-owning class?
Declaring a move constructor (or move assignment operator) suppresses — deletes — the implicitly generated copy constructor and copy assignment operator. This makes sense because a class that needs a hand-written move operation is very likely managing a resource directly, and a plain member-wise copy of that resource would almost certainly be wrong; the language avoids silently generating an incorrect copy in that situation.
Handle owns no dynamic resource but declares only a destructor. Does this code compile, and is the copy on the third line of main correct? Explain in terms of the Rule of Three.
It compiles, and in this specific case the copy is harmless: the compiler still generates a copy constructor (though that generation is a deprecated compatibility behavior once a destructor is user-defined), and since Handle's only member is a plain int with no owned resource, member-wise copying int id is correct. The Rule of Three would still recommend declaring the copy operations explicitly here to document intent, but the class happens not to be broken the way a pointer-owning class would be.
Write a class named Counter that wraps a dynamically allocated int (allocated with new int(0) in the constructor) and follows the Rule of Five: a destructor that frees it, a deep-copying copy constructor and copy assignment operator, and a move constructor and move assignment operator that transfer ownership and leave the source's pointer null. Provide an increment() method that adds 1 to the pointed-to int, and a value() method that returns it.
a's owned int reaches 2 after two increments. Counter b = a deep-copies it (allocating a new int with the same value), so b's further increment only affects b's own int. Output: 2 3. Since count is never null for a live, non-moved-from Counter, copy assignment can safely write through *count rather than reallocating.
Assuming the compiler performs no copy elision for this call (treat it as if a move genuinely occurs), what sequence of the four printed words would you expect, and why does 'copy' not appear?
The expected order is: acquire (constructing the temporary inside make), move (transferring it into r, since a temporary is an rvalue and move is preferred over copy when available), release (r destroyed at the end of main). 'copy' never appears because the source (make()'s temporary) is an rvalue, and overload resolution prefers the move constructor over the copy constructor whenever a viable move constructor exists for an rvalue source. In practice, modern compilers would likely elide this move entirely under mandatory copy elision rules for return values, printing only acquire and release, but the question asks you to reason about the non-elided case.
Write a movable-only class named Lock that wraps a bool* flag pointer set to true in its constructor and false in its destructor (simulating acquiring and releasing a lock). Delete its copy constructor and copy assignment operator, and provide a move constructor and move assignment operator that transfer the flag pointer and null out the source.
Lock a(&locked) sets locked to true. Lock b = std::move(a) transfers ownership of the flag pointer to b and nulls out a's pointer, so locked is unaffected by the move itself (still true). When main ends, only b's destructor sets locked back to false (a's destructor sees a null flag and does nothing), because ownership — not the flag's value — was what moved. Output: 1 1.
class Handle
{
public:
Handle(int id) : id(id) {}
~Handle() { std::cout << "closing " << id << '\n'; }
private:
int id;
};
int main()
{
Handle a(1);
Handle b = a;
return 0;
}struct Resource
{
Resource() { std::cout << "acquire "; }
~Resource() { std::cout << "release "; }
Resource(const Resource&) { std::cout << "copy "; }
Resource(Resource&&) noexcept { std::cout << "move "; }
};
Resource make() { return Resource{}; }
int main()
{
Resource r = make();
return 0;
}