[HN Gopher] A safe, non-owning C++ pointer class
___________________________________________________________________
A safe, non-owning C++ pointer class
Author : niekb
Score : 33 points
Date : 2025-09-25 17:28 UTC (3 days ago)
(HTM) web link (techblog.rosemanlabs.com)
(TXT) w3m dump (techblog.rosemanlabs.com)
| mwkaufma wrote:
| > Note that our use case is in a single-threaded context. Hence,
| the word safe should not be interpreted as 'thread-safe.' Single-
| threadedness greatly simplifies the design; we need not reason
| about race conditions such as one where an object is
| simultaneously moved and accessed on different threads. Extending
| the design to a thread-safe one is left as an exercise to the
| reader.
|
| Why intentionally design a worse alternative to std::weak_ptr
| which has been around since C++11??
| TinkersW wrote:
| They never mention std::weak_ptr which makes me think they
| aren't aware of it.. yes this looks pretty useless and
| unsafe(isn't everything multi-threaded these days..)
| niekb wrote:
| > isn't everything multi-threaded these days..
|
| There are alternative ways to utilize a machine with multiple
| cores, e.g. by running one thread per CPU core, and not
| sharing state between those threads; in each such thread you
| then have single-thread "semantics".
| mwkaufma wrote:
| weak_ptr supports this -- it's only mt-safe if you
| specialize it with std::atomic
| spacechild1 wrote:
| Multi-threading does not imply shared ownership, it can also
| be achieved with message passing.
| niekb wrote:
| (Author here.) That is a good question. For our use case, we in
| fact do not use std::shared_ptr in our implementation, but
| instead a single-threaded shared_ptr-like class that has no
| atomics (to avoid cross-core contention). However, when I wrote
| the blog-post, I replaced that not-so-well-known class by
| std::shared_ptr for the sake of accessibility of the blogpost
| for a general c++ audience, but by doing so, it indeed becomes
| a natural question to ask why one wouldn't use std::weak_ptr
| (which I hadn't realised when writing the post).
|
| One reason why this design can still be beneficial when using
| the standard std::shared_ptr in its implementation, is when you
| do not want to manage the pointee object by a std::shared_ptr
| (which is a requirement if you want to use std::weak_ptr).
| E.g., if you want to ensure that multiple objects of that type
| are laid out next to each other in memory, instead of scattered
| around the heap.
|
| Another goal of the post is to show this idea, namely to use a
| shared_ptr<T*> (instead of shared_ptr<T>), which is kind of
| non-standard, but can be (as I hope I convinced you) sometimes
| useful.
| cherryteastain wrote:
| > but instead a single-threaded shared_ptr-like class that
| has no atomics (to avoid cross-core contention
|
| Why would there be contention in a single threaded program?
| izabera wrote:
| atomics aren't free even without contention. the slogan of
| the language is "you don't pay for what you don't use", and
| it's really not great that there's no non atomic refcount
| in the standard. the fact that it _is_ default atomic has
| also lead people to assume guarantees that it doesn 't
| provide, which was trivially predictable when the standard
| first introduced it.
| eMSF wrote:
| Related to this, GNU's libstdc++ shared_ptr
| implementation actually opts not to use atomic arithmetic
| when it infers that the program is not using threads.
| grogers wrote:
| People assume non-existent guarantees such as?
| izabera wrote:
| "is shared_ptr thread safe?" is a classic question asked
| thousands of times. the answer by the way is "it's as
| thread safe as a regular pointer"
| loeg wrote:
| OP specifically mentioned contention, though -- not
| marginally higher cost of atomic inc/dec vs plain
| inc/dec.
|
| > For our use case, we in fact do not use std::shared_ptr
| in our implementation, but instead a single-threaded
| shared_ptr-like class that has no atomics (to avoid
| cross-core contention).
|
| A single-threaded program will not have cross-core
| contention whether it uses std::atomic<> refcounts or
| plain integer refcounts, period. You're right that non-
| atomic refcounts can be anywhere from somewhat cheaper to
| a lot cheaper than atomic refcounts, depending on that
| platform. But that is orthogonal to cross-core
| contention.
| mwkaufma wrote:
| > laid out next to each other in memory
|
| Moving goalpost. But just to follow that thought: Decoupling
| alloc+init via e.g. placement-new to do this introduces a
| host of complications not considered in your solution.
|
| If that layout _is_ a requirement, and you don't want a
| totally nonstandard foundation lib with nonstandard types
| promiscuously necessitating more nonstandard types, you want
| a std::vector+index handle.
| abstractspoon wrote:
| > Extending the design to a thread-safe one is left as an
| exercise to the reader.
|
| Doesn't get much glibber than that!
| niekb wrote:
| That was mostly meant as irony/a joke, but I admit that's not
| really clear from the text... For the sake of clarity, if you
| need thread-safety, probably best to just use std::shared_ptr /
| std::weak_ptr.
| Kranar wrote:
| It's a common misconception that std::shared_ptr is thread
| safe. The counter is thread safe, but the actual shared_ptr
| itself can not be shared across multiple threads.
|
| There is now atomic_shared_ptr which is thread safe.
| delduca wrote:
| It is now a template specialization of atomic
| std::atomic<std::shared_ptr<T>>.
| coneonthefloor wrote:
| I've recently read the third edition of Bjarne's "A tour of c++"
| (which is actually a good read). I feel the author of this post
| could benefit from doing so also.
| w4rh4wk5 wrote:
| For situations like this, I prefer a generational index where the
| lookup fails if the object has been destroyed. For context, the
| "manager" that holds the objects referred to typically has a
| lifetime of the whole program.
| jayde2767 wrote:
| This just seems intentionally bad to show where Rust would be
| better. This is yet another example of what I call "corner-case"
| instruction, which I define as, "I am going to take an obviously
| terrible corner-case that shows what an awful developer can do
| that will break a program, then demonstrate my brilliance by
| introducing my (highly-biased) opinionated point I wanted to
| make..."
|
| In this particular case, it was subtly, Rust is preferred because
| it doesn't allow unsafe memory operations such as the one
| demonstrated. Really, all it demonstrates is that you can create
| really bad C++.
| vasilvv wrote:
| This sounds very similar to how base::WeakPtr works in Chromium
| [0]. It's a reasonable design, but it only works as long as the
| pointer is only accessed from the same thread it is created.
|
| [0]
| https://chromium.googlesource.com/chromium/src/+/HEAD/base/m...
| wyldfire wrote:
| std::span<> is another option. Especially when paired with
| libc++'s hardening mode(s). Apparently, Google has deployed them
| in production.
___________________________________________________________________
(page generated 2025-09-28 23:00 UTC)