We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Copy, Move, and the Rule of Zero step 4 of 5
Implement copying and moving for an owned handle
An integer handle can identify a resource without owning it. If a wrapper closes that resource in its destructor, simply copying the integer creates two wrappers that both try to close the same resource.
The Rule of Five asks you to consider the destructor, copy constructor, copy assignment, move constructor and move assignment together. It does not require every wrapper to support copying. A file or socket wrapper might deliberately delete its copy operations. This exercise instead requires a copyable Handle whose copies own independent resources with equal values.
The supplied API
The capi namespace simulates resource management:
- open_handle(value) creates a resource and returns a positive integer ID.
- handle_value(id) reads a live resource's value.
- close_handle(id) marks a live resource closed.
- open_count() counts resources still open.
Reading or closing an invalid or already-closed ID increments capi::errors. ID 0 is reserved for an empty wrapper and must never be passed to those read/close operations. The simulated API uses value -1 internally to mark closed slots, so exercise inputs exclude -1. Values, IDs and counts fit int.
Your task
Complete Handle while leaving capi and exercise unchanged. Use id_ == 0 as the empty state and implement these behaviors:
| Operation | Required behavior |
|---|---|
| Destructor | Close the owned resource once, or do nothing if empty. |
| Copy constructor | Open a new resource with the source's value; copying an empty source stays empty. |
| Copy assignment | Replace the destination with an independent copy; preserve its old resource if acquiring the copy throws. |
| Move constructor | Transfer the ID without opening a resource, then empty the source. |
| Move assignment | Release the destination's current resource, transfer the ID and empty the source; self-move leaves the object unchanged. |
Copy assignment must also handle self-assignment safely. An empty Handle must remain safe to destroy, copy from, move from and assign to. value() is for non-empty Handles; the supplied exercise never reads a moved-from wrapper. Empty-source assignment should release any old destination resource and leave the destination empty.
Acquire a copy before releasing the old resource
Copy-and-swap is one way to implement copy assignment:
Handle& operator=(const Handle& other) {
Handle tmp(other); // may throw before this object changes
swap(tmp); // exchange IDs without throwing
return *this; // tmp now owns and releases the old resource
}
If acquisition fails, the destination still owns its original resource. For self-assignment, the temporary first owns a separate equal-valued resource; after swapping, it destroys the original resource. This preserves the value without requiring both wrappers to own the same ID.
Make these moves noexcept
This wrapper's move operations only transfer integer IDs and, for assignment, call the non-throwing close_handle. Mark them noexcept because those operations do not throw for supported inputs.
During vector growth, a potentially throwing move can cause existing elements to be copied when copying is available, helping preserve the vector on failure. A noncopyable type may still need to be moved even if moving can throw, with weaker guarantees in that failure case. Do not add noexcept to an operation that can actually let an exception escape just to encourage moving.
What exercise returns
The supplied function grows a vector of Handles, reads their values, then copies the first Handle, moves that copy, and move-assigns it over the last Handle. For non-empty input, its output is the original values followed by the first value three more times.
For [10, 20, 30], the harness reports:
[[10, 20, 30, 10, 10, 10], 0, 0]
The last two entries are the number of resources still open and the API error
count, measured after exercise returns. Both must be zero. Empty input returns
[[], 0, 0]; zero and repeated values are valid inputs.
The starter's destructor suppresses implicit moves, while its generated copies duplicate IDs. That breaks exclusive ownership. Correct the class's ownership operations rather than altering the API counters or the exercise sequence. The provided sequence does not call copy assignment or self-assignment, so check those required operations separately as well.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
C++ reference solution
Sign in to track your attempts toward unlocking the reference solution.