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
Owning something raw costs you five functions
The rule of zero holds until you have to wrap a C API — a file descriptor, a socket, an OpenGL texture, a handle from a library that predates the standard library. Then somebody has to write the destructor, and the moment you do, the compiler’s memberwise defaults are wrong.
Wrong in a specific way. Your member is an int handle, and the generated
copy constructor copies the int. Now two objects hold the same handle, both
destructors run, and the resource is closed twice. That is a double free with
a different noun.
The rule of five: if you write any of the five special members, write all five.
Because writing one changes what you get for the rest — a destructor suppresses the moves, as the previous problem showed — the safe position is to be explicit about the whole set.
What each one has to do
| Obligation | |
|---|---|
| Destructor | Release what you hold — and only if you still hold it. |
| Copy constructor | Acquire a second resource. Duplicating the handle is the bug. |
| Copy assignment |
Release yours, take a copy of theirs, and survive x = x. |
| Move constructor | Take theirs, and leave the source empty so its destructor does nothing. |
| Move assignment | Release yours, take theirs, empty the source, survive self-move. |
The two lines that people leave out are the last halves of the move operations. A move that steals the handle and leaves the source holding it too has not moved anything — it has duplicated it, and you are back to the double close.
noexcept is not decoration
Move operations should be marked noexcept, and here is the concrete reason:
when a std::vector grows, it must relocate its elements. If the move
constructor is noexcept it moves them. If it is not, the vector copies
them instead — because it cannot offer the strong exception guarantee any
other way. An un-noexcept move constructor silently turns every
reallocation back into a deep copy. performance-noexcept-move-constructor
is in the gate for exactly this.
Copy-and-swap, briefly
A tidy way to write copy assignment:
Handle& operator=(const Handle& other) {
Handle tmp(other); // may throw — nothing has changed yet
swap(tmp); // cannot throw
return *this; // tmp's destructor releases the old resource
}
Self-assignment works without a check, and it gives the strong guarantee:
if the copy throws, *this is untouched. Track 2 promised you would meet
this — here it is.
Your task
A C-style API is given: open_handle, handle_value, close_handle. It
records an error every time it is handed a handle that is closed or was never
valid, and it reports how many handles are still open.
Write Handle so that it wraps one of these correctly. exercise is given
and does the things that break a half-written wrapper: it fills a vector that
reallocates, copies, moves, and move-assigns.
The starter is the version everyone writes first — a constructor and a destructor, and nothing else. The harness reports the values, the handles left open, and the API’s error count. All three must come out right.
Stuck?
C++ reference solution
Sign in to attempt this problem and reveal the reference solution.