In a reference-counted object graph, what happens to a weak handle when the last owning handle to its object is dropped?
answer
- only owners are counted
- a claim versus a question
- zero owners means destroy now
- weak handles read absent afterwards
- shared record outlives the payload
basics
~20 sIt reads as absent from that moment on. Only owning handles are counted, so the last one dropping destroys the object immediately; weak handles are not counted, cannot delay that, and are never allowed to yield destroyed storage.
solid answer
~50 sOnly owning handles contribute to the count that decides liveness. When the last one is dropped the count reaches zero and the object is destroyed there and then, at the point of the drop, releasing the owning handles it held in turn. A weak handle is not counted, so it cannot postpone any of that; what it gets instead is a guarantee that it will never hand you a destroyed object — from that moment every weak handle to it reads as absent. Implementations get that guarantee by routing weak handles through a small shared control record that holds the counts, so destroying the payload only has to clear one pointer in one place. The record itself survives until the weak handles are gone too, which is why a weak handle still pins a little metadata after its object has died.
code
pseudocode · 14 linesrelease_owning(handle):
record = handle.record
record.owning = record.owning - 1
if record.owning == 0:
destroy_payload(record.payload) # runs now, on this thread
record.payload = NONE # weak handles now read absent
if record.weak == 0:
free(record) # no observers left either
release_weak(handle):
record = handle.record
record.weak = record.weak - 1
if record.weak == 0 and record.owning == 0:
free(record)go deeper
Remember the one-line split: an owning handle is counted and keeps the object alive, a weak handle is not counted and merely watches. Zero owners means the object is destroyed right then.
Explain the mechanics: two counts in one shared record, the payload destroyed at zero owners, the record freed only once the weak handles are gone too, and destruction cascading into the handles the object held.
Show what this costs in a running system — destruction cascades inline on the dropping thread, and a registry of weak handles to dead objects still grows until you prune it.
Frame the trade you are buying: deterministic release at the exact moment of the last drop, paid for with an indirection, an extra count and an absent path that every observer has to implement.
## The count only counts owners A reference-counted object carries a number — in a header word on the object, or in a side record keyed by its address — recording how many **owning handles** currently refer to it. Copying an owning handle raises that number; destroying or reassigning one lowers it. The number is the object's licence to exist: while it is above zero, some part of the program has declared that it needs the object. A **weak handle** deliberately stays out of that number. It refers to the object without asserting that the object should continue to exist. That is the whole difference in one line: **an owning handle is a claim, a weak handle is a question.** ## What happens at the moment of the last drop 1. The last owning handle is destroyed or reassigned and the owning count goes from one to zero. 2. The object's destruction hook runs immediately, on whichever thread performed that drop — not at some later collection point. 3. The owning handles that the object itself held are released in turn, so one drop can cascade through a whole subgraph. 4. The payload's storage is no longer valid to read. 5. Every weak handle to that object must, from now on, answer "gone". Step 5 is the one candidates skip, and the interesting question is *how*. Nothing walks the heap hunting for weak handles to null out one by one — that would need a back-pointer list per object and would cost about as much as tracing. Instead every weak handle reaches the object indirectly, through one shared record, and destruction clears a single field in that record. ## Why a weak handle is an indirection, not a bare address The shared record typically holds two numbers: the **owning count**, which decides when the payload is destroyed, and a **weak count**, which decides when the record itself may be freed. Two counts mean two lifetimes: - the object's lifetime ends when the owning count hits zero; - the record's lifetime ends when the last weak handle goes away as well. | | owning handle | weak handle | unchecked non-owning handle | |---|---|---|---| | counted toward liveness | yes | no (counted separately, for the record) | no | | keeps the payload alive | yes | no | no | | after the payload dies | cannot happen | reads as absent | dangles; reading it is undefined | | cost at each use | none | a liveness check, usually a promotion | none | Designs differ in where that record lives. Some allocate the record and the payload together in one block, which means a single surviving weak handle keeps the whole block reserved even though the payload's contents are destroyed. Others allocate the record separately, so only a few bytes survive but every object that might ever need a weak handle pays an extra allocation or an extra word. Ecosystems make this trade differently; the mechanism is the same either way. ## The cost that survives the object A weak handle is cheap, not free, and it is not free *after* the object dies either: - It pins the shared record. A registry that accumulates weak handles to long-dead objects still grows; you prune dead slots, you just do not leak the objects themselves. - Each use costs a branch, and a **correct** use costs a promotion to a temporary owning handle, because liveness can change between the test and the read. - The code that holds it must carry an "absent" path. That path is real program behaviour someone has to design: re-fetch, clear the selection, skip the notification. ## Where designers get this wrong The common error is reading "weak" as a schedule rather than as a strength — believing the object is freed *later* for weak holders, or that a weak handle delays destruction until it is convenient. It does neither. The second error is expecting resurrection: once the owning count has reached zero the object is gone, and a weak handle that later finds the count at zero cannot revive it. The third is assuming the record disappears with the object, which is why a leak audit that counts only live objects can miss a pile of retained metadata. The payoff for getting it right is deterministic: the moment the last genuine owner releases an object, the memory goes back, and every observer that was merely watching discovers it politely rather than by reading rubble.
- Why does the shared record have to outlive the object it describes?Because the weak handles are still out there, and each of them has to be able to find out that the object is gone. They reach the object only through the record, so the record is the one place that can answer. Freeing it with the payload would leave every surviving weak handle reading freed memory — exactly the failure weak handles exist to prevent.
- If a weak handle never keeps the object alive, can a program leak through weak handles?Not the objects, but yes the metadata. Every weak handle pins its shared record, so a cache or registry that keeps adding weak entries and never prunes the dead ones grows without bound. Where a design places the record inside the same allocation as the payload, the dead payload's storage is held too. Prune dead slots on a schedule or on access.
- Does destruction at count zero happen on a particular thread?On whichever thread performed the drop that took the count to zero. That is worth knowing because the destruction cascade — and any work the destructor does — runs inline on that thread, so a single release can cost far more than a decrement if it tears down a large subgraph.
A library card holds a book on loan; a note in your diary saying "chapter 4 of that book" does not. When the last loan ends the book goes back on the shelf, and your note now points at a title, not a copy in your hands.
saying these in an interview costs you the question
- Says weak handles are freed later than owning ones, as a schedule
- Believes the runtime hunts down and nulls each weak handle individually
- Thinks a weak handle delays destruction until it is convenient
- Expects a weak handle to revive an object whose count reached zero
- Assumes the shared control record dies with the payload