Skip to content
VLSI Mentor

C++ Part I · Module 10 · Dynamic Memory and Object Lifetime

Ownership: The Question new and delete Cannot Answer

The previous chapter fixed three leaks by moving one statement into a destructor, and deliberately did not name what made that work. It was not the destructor, which is only a mechanism. It was a decision taken implicitly the moment that class was written — that this object, and nothing else in the program, was responsible for that array, and that the array would last exactly as long as the object did. Stated plainly, that decision turns out to be what every failure in this module has actually been about. A forgotten release is nobody taking responsibility. A release that ran twice is two pieces of code both taking it. A pointer used after its object ended is one piece of code assuming another had not finished. A leak on an error path is responsibility that was accepted and then not discharged. Five measured failures, one missing idea, and the idea has a name. This chapter states the question in its exact form, re-reads the module's evidence through it, and then makes an argument that is stronger than a recommendation: that the two operators cannot express the answer even in principle, because the information has nowhere to live. What C++ does instead is a single sentence, and the rest of this curriculum is its development.

Advanced18 min readC++Part IFundamentalsMemory

Part I · Module 10 · Chapter 10.9 · Ownership: The Question new and delete Cannot Answer

1. What Made the Last Chapter's Fix Work

Chapter 10.8 §7 took three leaking functions to zero leaks with this class:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    class Buffer {
    public:
        explicit Buffer(int n) : data_(new int[n]) {}
        ~Buffer() { delete[] data_; }
        // ...
    private:
        int* data_;
    };

The destructor is the mechanism. The decision is elsewhere, and it was never written down:

This object, and nothing else, is responsible for that array — and the array lasts exactly as long as this object does.

That decision has a name, and naming it is this chapter's job.

2. The Question

Ownership is the answer to one question about every resource:

Which code is responsible for releasing this, and when?

It has three parts, and all three must have answers:

Who. Exactly one piece of code, identifiable. Not "the caller usually" or "whoever finishes last".

When. At a specific, determinable moment — ideally one guaranteed by the language rather than chosen by a programmer.

And what everyone else is. Every other piece of code holding a pointer to that object is a borrower, and must not release it, and must not use it after the owner is finished.

A resource with a clear answer to all three is owned. One without is where this module's bugs live.

3. The Module's Five Failures, Re-Read

Every measured failure in Module 10 is an ownership failure. This is the chapter's central claim and it is worth checking against the evidence rather than asserting.

The leak (Chapter 10.6) — nobody owned it. Measured: 195,808 KB against a flat 1,328 KB, and 20,000 leaked objects for 20,000 iterations. Responsibility was never assigned, so it was never discharged.

The double delete (Chapter 10.7) — two owners. Measured: exit 133, SIGTRAP. Two pieces of code each believed they were responsible. The commonest real cause is a copied handle, where the copy was not a decision anybody made.

The use-after-free (Chapter 10.7) — a borrower outlived the owner. Measured: 42 became 5, the write "succeeded", exit 0. The borrower had no way to know the owner had finished, because a pointer carries no such information.

The control-flow leak (Chapter 10.8) — ownership accepted and not discharged. Measured: 3 leaks, 96 bytes, 0 compiler warnings, with the delete plainly visible. The function took responsibility at the new and had three exits where it did not honour it.

And the mismatched release (Chapter 10.4) — the owner did not know what it owned. Measured: delete on a new[] run ran 1 of 3 destructors then exit 133; the reverse direction printed "survived" and exited 0, silent even under a sanitizer. Responsibility was held, and the information needed to discharge it correctly was not.

Five bugs, five shapes, one missing fact. And notice that "be more careful" addresses none of them, because in each case the care would have to be exercised by code other than the code containing the bug.

4. Why new and delete Cannot Answer It

This is the structural argument, and it is stronger than a preference.

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
    int* p = new int(42);

Ask the three questions of p.

Who owns it? The type is int*. Chapter 10.4's callout established that this exact type is produced by new int, by new int[n], by &someLocal, by a decayed array (Chapter 9.2), and by malloc — each needing a different release or none. The type does not record which. It also does not record whether the holder is the owner or a borrower: an owning pointer and a borrowed pointer are the same type.

When should it be released? There is no answer in the code, only in a programmer's intention. Chapter 10.8 measured what happens to intentions when control flow changes: three edits, three leaks, zero warnings.

What is everyone else? Unanswerable. Any number of copies of p may exist and none is distinguishable from the original.

So the information has nowhere to live. It is not that new and delete express ownership badly — they have no place to put it. A pointer's type says where an object is and says nothing about whose job it is to end it.

That is why Chapter 10.3's callout could say the correctness of delete p is a whole-program property, and why Chapter 10.7's could sharpen it: the correctness of a delete cannot be determined by reading the function it appears in. The facts needed are not expressible in the types involved, so they are not anywhere the compiler or a reader can check.

5. The Three Patterns That Actually Occur

Real programs need exactly three answers to "who", and it is worth knowing them before meeting the types that implement them.

Exclusive ownership — exactly one owner. The common case by a wide margin. One piece of code is responsible; the resource ends when that code is done. Chapter 10.8's Buffer is this, and transferring it means the old owner stops being the owner.

Shared ownership — several owners, none last. The resource ends when the last one is finished, which requires counting them, because no single owner knows whether it is the last. Genuinely needed sometimes, and over-used because it appears to remove the need to decide.

Borrowing — no ownership at all. Access without responsibility, valid only while some owner is still alive. This is what a const T& parameter is (Chapter 6.5), what a std::string_view is (Chapter 9.6 — whose §5 measured the dangling case), and what a raw pointer should mean in modern code.

Two things worth noticing. First, the majority of pointers in a well-designed program are borrowers, and the ownership question for them is answered by "not mine" — which is a complete answer. Second, Chapter 10.2 §9's list of what is safe to return was really a list of borrowing rules: you may hand back a reference to something whose owner outlives the call.

6. How Ownership Is Expressed Today in Code Like This

Since the types cannot carry it, manual-allocation code conveys ownership in exactly three ways, all of which fail differently:

In a comment. // caller must delete. Not checked, not propagated, and routinely false after a refactor.

In a naming convention. createWidget returns something you own; getWidget does not. Better than nothing, enforced by nothing, and silently violated by the first function that does not follow the convention.

In documentation elsewhere. Which the caller has not read.

All three put a machine-checkable fact in a place no machine can check. Chapter 10.8's measurement is the consequence: the author of the early return was not being careless — the information they needed was in a comment, or in no place at all.

7. The Answer

C++'s answer is one sentence:

Tie the resource to an object's lifetime, and let the language run the release.

Make some object the owner. Acquire the resource when that object is constructed; release it in its destructor. Then the "when" is no longer a decision anyone makes at a call site — it is the end of the owning object's lifetime, which Chapter 10.5 §5 established is decided by the compiler and Chapter 10.8 §8 measured happening on paths nobody wrote: destructors ran innermost first, during exception propagation, before the catch body.

Why that answers all three parts of §2:

Who — the owning object, named, with a type that says so.

When — when that object's lifetime ends, guaranteed, on every path.

Everyone else — holds a reference or a raw pointer, and raw now means borrower, because owners have their own types.

And why it fixes the five failures of §3: there is exactly one owner so no double delete; the owner cannot forget because the compiler runs the destructor; there is no path that skips it; and the owner knows what it owns, so it uses the right release form.

The measured proof is Chapter 10.8's comparison, with no change to control flow: 3 leaks and 96 bytes became 0 leaks and 0 bytes.

Who develops this. This chapter names the question and states the answer; it is not the technique.

  • Chapter 11.1 — Why Classes Exist: Invariants, Not Just Grouping — is why a type can hold a promise at all. "I own this" is an invariant, and Chapter 9.11 §9 showed that an aggregate cannot enforce one.
  • Chapter 12.7 is destructors properly, and Chapter 12.9 — RAII: Tying a Resource to an Object's Lifetime — is this sentence as a technique, with the name it is usually given.
  • Module 13 is what Chapter 10.8 §7's two = delete lines were standing in for: an owning object must say what happens when it is copied or moved. Chapter 13.4 — Shallow Copy, Deep Copy and the Double Free That Follows — is Chapter 10.7's copied handle, properly treated.
  • Module 14 — Smart Pointers and Ownership — is the ready-made types: std::unique_ptr for §5's exclusive case, std::shared_ptr for the shared one, std::weak_ptr for breaking the cycles that shared ownership creates. Chapter 14.1 is Ownership as an API Decision, and Chapter 14.7 is the modern meaning of a raw pointer: Observers, Not Owners.
  • And Chapter 13.11 — The Rule of Zero — is the destination: the best owning class is usually one you did not write, because a member that already owns correctly leaves you nothing to manage.

8. What to Actually Do

Ask "who owns this, and when does it end?" for every resource. If you cannot answer from the code, treat it as a defect.

Give every resource exactly one owner unless you have a reason for shared ownership — and be suspicious of that reason.

Treat every other pointer as a borrower, valid only while the owner lives.

Decide ownership before writing new, not after.

Do not express ownership in comments or naming conventions. Both are unchecked; Chapter 10.8 measured the result.

Prefer designs with no ownership question — automatic storage, return by value, standard containers.

Put the release in a destructor whenever you must manage a resource yourself.

Expect a raw pointer in modern code to mean "borrowed" (Chapter 14.7) and be suspicious when one appears to own.

And generalise past memory. A file handle, a lock, a socket and a vendor API handle all pose exactly this question, and none of them has a sanitizer.

9. Mistakes Worth Making Once

Allocating without deciding who owns it. Chapter 10.6's 20,000 leaks.

Two pieces of code owning one object. Chapter 10.7's exit 133.

Using a borrowed pointer after the owner finished. Chapter 10.7's 42 became 5, exit 0.

Accepting ownership and not discharging it on every path. Chapter 10.8's 3 leaks, 0 warnings.

Owning something without knowing what it is. Chapter 10.4's 1 of 3 destructors.

Documenting ownership in a comment. Unchecked, and false after the next refactor.

Reaching for shared ownership to avoid deciding. It defers the decision and adds reference cycles (Chapter 14.6).

Assuming a raw pointer owns, or assuming it does not, without checking.

Thinking this is only about memory. The same question governs files, locks and handles.

10. Interview Relevance

"What is ownership?" Which code is responsible for releasing a resource, and when — plus the fact that everything else is a borrower. Candidates who answer only with unique_ptr have named a tool rather than the idea.

"Why aren't new and delete enough?" The structural answer is the strong one: a pointer's type does not record who owns the object, when it should end, or whether this holder is an owner or a borrower — so the information has nowhere to live, and the correctness of a delete is therefore not visible in the function containing it.

"What are the ownership models?" Exclusive, shared, and borrowing — with the observation that most pointers in good code are borrowers, and that shared ownership is the one most often chosen to avoid making a decision.

"What is RAII and why?" Tie the resource to an object's lifetime so the language runs the release. The reason is the one Chapter 10.8 measured: destructors run on every path including exception unwinding, so the release cannot be skipped by control flow added later.

"Have you had to debug a leak or a use-after-free?" The best answers describe the ownership confusion that caused it — two owners, or a borrower outliving an owner — rather than only the symptom.

11. What You Should Be Able to Say Now

  1. Ownership answers: which code releases this, and when — plus what everyone else is.
  2. Three parts: who (exactly one), when (a determinable moment), and everyone else (borrowers).
  3. All five of this module's failures are ownership failures: no owner (leak), two owners (double delete), borrower outliving owner (use-after-free), ownership not discharged on every path (control-flow leak), owner not knowing what it owns (mismatched release).
  4. Measured anchors: 195,808 KB vs 1,328 KB · exit 133 · 42 became 5 with exit 0 · 3 leaks/96 bytes/0 warnings · 1 of 3 destructors.
  5. new and delete cannot express ownership — a pointer's type records neither provenance nor responsibility, and an owning and a borrowed pointer are the same type.
  6. So the correctness of a delete is not determinable from its own function.
  7. The three patterns are exclusive, shared and borrowing, and most pointers should be borrowers.
  8. Comments and naming conventions cannot carry ownership — unchecked and routinely false.
  9. The answer: tie the resource to an object's lifetime and let the language run the release.
  10. It works because destructors run on every path, measured innermost-first during unwinding.
  11. Measured proof: 3 leaks → 0 leaks with the control flow unchanged.
  12. Developed by: 11.1 (invariants), 12.7/12.9 (destructors and RAII), Module 13 (copying an owner), Module 14 (the ready-made types), 13.11 (the Rule of Zero).
  13. The question generalises to files, locks, sockets and handles — where no sanitizer helps.
  14. This is not a flaw in C++ — it is the price of deciding when things end, and the answer puts that decision in the type system.

12. Exercises

Exercise 1 — the three parts. For Widget* w = factory();, write the three ownership questions and say which of them the declaration answers. (The answer is none; say why for each.)

Exercise 2 — classify the failures. For each of this module's five measured bugs, state which part of §2 was unanswered and give the measurement.

Exercise 3 — same type, different meaning. Give four ways to obtain an int* that require four different treatments, and say what that implies about reviewing a function that takes one.

Exercise 4 — pick the pattern. Choose exclusive, shared or borrowing, with reasons: (a) a buffer used by one function (b) a config object read by many subsystems for the program's life (c) a transaction handed from a generator to a scoreboard (d) a parameter that is only read (e) nodes in a graph that reference each other.

Exercise 5 — why the comment fails. // caller must delete sits above a function. List three ways this becomes false without anybody editing the comment.

Exercise 6 — remove the question. For each, redesign so no ownership question arises: (a) a function returning new std::string (b) a vector<Widget*> (c) a function allocating a scratch buffer.

Exercise 7 — the argument. A colleague says Module 10's bugs are a discipline problem solvable by code review. Rebut it using §4's structural argument and two measurements.

Exercise 8 — beyond memory. Restate the three ownership questions for a file handle, and say which of this module's five failures has an exact analogue there.

13. Summary

  • Ownership is the answer to: which code releases this, and when — with everything else a borrower.
  • All five measured failures in this module are ownership failures, and "be more careful" addresses none of them.
  • new and delete cannot express ownership: the type carries neither provenance nor responsibility, and owning and borrowed pointers are indistinguishable.
  • Hence a delete's correctness is not visible in its own function.
  • Three patterns: exclusive, shared, borrowing — and most pointers in good code borrow.
  • Comments and naming conventions are unchecked and therefore fail.
  • The answer: tie the resource to an object's lifetime and let the language run the release, which works because destructors run on every path.
  • Measured: 3 leaks and 96 bytes became 0 and 0, control flow unchanged.
  • Today you can already ask the question of every pointer, prefer designs that avoid it, and decide before allocating.
  • It generalises to every resource, and it is the price of controlling when things end — paid in the type system.

Everything this module has concluded now depends on something that does not exist yet. The answer in §7 requires an object that can make a promise and keep it: one that holds a resource, states that it is the owner, and guarantees the release in a destructor the compiler is obliged to run. Chapter 10.8's Buffer was a sketch of such a thing, and the two deleted lines in it were an admission that the sketch was incomplete. What is missing is the construct this curriculum has deliberately withheld until there was a reason for it — a type that enforces a rule rather than merely grouping data. Chapter 9.11 §9 identified the gap precisely: a struct is transparent by design, so nothing stops Field{40, 100} claiming a 100-bit field in a 32-bit register, and nothing could stop a caller reaching in and releasing a buffer out from under its owner. The next module opens with exactly that argument, and its first chapter is titled for it: classes exist for invariants, not just grouping.

Standards & specifications

Governing standard
ISO/IEC 14882 (the C++ language standard)(opens ISO/IEC (JTC 1/SC 22/WG 21) in a new tab)

Defines the C++ language and its standard library — the type system and templates, the object model and object lifetime, initialisation and overload resolution, the memory model and the containers, algorithms and utilities the library provides. Like C's standard it is a FAMILY of editions (C++98/03, C++11, C++14, C++17, C++20, C++23) and the editions are NOT interchangeable: a feature this curriculum relies on may be unavailable, differently specified or merely proposed in another edition, so each lesson names the edition its claim depends on. It does not define any foreign-function interface to a hardware description language, nor what a compiler actually emits, nor platform behaviour on real silicon — those are the implementation's and the device's contracts. Working drafts published by WG21 are freely available and are what most engineers read day to day; a draft is not the published standard.

This page also covers RTL structure, verification approach and debugging technique. Those are engineering practice built on the standard, not requirements the standard itself imposes.

Where this fits

Part of the C++ Programming curriculum.