[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)