A Raw Owning Pointer in C++ Is a Review Blocker
A raw owning pointer in C++ is a review-blocking bug. Not a style nit, not a "we'll clean it up later" — a blocker. Manual memory management without a wrapper is exactly where leaks, double frees, and use-after-free bugs hide, and it is the single easiest red flag to enforce mechanically: if a T* owns what it points to, the review stops until it doesn't.
This matters twice as much now that coding agents write a large share of your diffs. Models trained on decades of pre-C++11 code will happily emit new in one function and delete in another. Your job in review is not to trace every path between them — it's to reject the pattern outright.
Why "owning" is the keyword
A raw pointer itself is fine. The C++ Core Guidelines are explicit: R.3 — a raw pointer (a T*) is non-owning. The pointer that observes an object someone else owns is idiomatic and zero-cost. The bug class starts the moment a raw pointer is also responsible for calling delete:
- Every early return is a leak. An exception or a forgotten
returnpath betweennewanddeleteleaks silently. - Ownership is invisible at the call site.
process(widget*)— does it take ownership? You can't tell from the signature, so someone eventually frees twice, or never. - It defeats the type system. The compiler enforces nothing about a
T*'s lifetime; aunique_ptr<T>turns the same contract into code the compiler checks.
That's why Core Guideline R.11 says avoid calling new and delete explicitly at all in application code.
The replacement table
| You wrote | Write instead | Why |
|---|---|---|
T* p = new T(...) + delete p | auto p = std::make_unique<T>(...) | Exclusive ownership, freed on every exit path (R.20) |
| "Two places need it alive" | std::shared_ptr<T> — only for genuinely shared lifetime | Refcounting has a cost; R.21 says prefer unique_ptr |
| Pointer + length parameters | std::span<T> (C++20) | A bounds-aware non-owning view (F.24 / I.13) |
| Just looking, never freeing | T*, T&, or const T& | Non-owning by convention (R.3, F.7) |
| Hand-written destructor/copy/move | Rule of zero — let members manage themselves | If every member is RAII, you write none of the five (C.20) |
The decision procedure fits in one sentence: unique by default, shared only when lifetime is genuinely shared, views for everything else.
Power tricks: enforcing it on agents
- Put it in the agent's instructions. One line in your
CLAUDE.md/ system prompt — "Raw owning pointers are forbidden; useunique_ptr,shared_ptronly for shared lifetime,span/references for views; prefer rule-of-zero types" — prevents the pattern instead of catching it. - Make the toolchain the reviewer.
clang-tidyships checks that automate exactly this:cppcoreguidelines-owning-memoryflags raw owning pointers, andmodernize-make-uniquerewritesnewintomake_unique. Wire them into CI so the block is automatic, not social. - Grep before you review.
grep -rn "new \|delete " src/on an AI-generated diff takes five seconds and catches most offenders before you read a line of logic. - Escape hatch, labeled. Interop with C APIs sometimes forces raw ownership at the boundary. Wrap it immediately (
unique_ptrwith a custom deleter) and keep the raw handle's scope to one function.
The payoff is the reel's closer: ownership becomes explicit, and an entire bug class stops being expressible. When your agent emits a raw owning pointer — block it.
Resources
- C++ Core Guidelines R.11 — Avoid calling
newanddeleteexplicitly - C++ Core Guidelines R.3 — A raw pointer (a
T*) is non-owning - C++ Core Guidelines C.20 — the rule of zero
std::unique_ptr— cppreferencestd::span— cppreference- clang-tidy
cppcoreguidelines-owning-memorycheck
Building an AI feature? Yeda AI designs, audits, and ships production LLM systems.