• weak_ptr reference count on shared_ptr

    From boltar@3:633/10 to All on Mon Jun 1 10:19:58 2026
    I have a system that has a block of central shared_ptrs each of which is references by numerous weak_ptr's all over the place. However when there are
    no further weak_ptr's referencing the shared_ptr can be deleted. However there seems to be no built in way in C++ to get the number of weak_ptr's
    referencing a shared as use_count() only counts other shared_ptr's.

    Does anyone know of a clean way to get the weak reference count or will
    I have to roll my own (which rather defeats the point of using weak+shared in the first place)?


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Mon Jun 1 16:02:35 2026
    On 01/06/2026 12:19, boltar@caprica.universe wrote:
    I have a system that has a block of central shared_ptrs each of which is references by numerous weak_ptr's all over the place. However when there are no further weak_ptr's referencing the shared_ptr can be deleted. However there
    seems to be no built in way in C++ to get the number of weak_ptr's referencing a shared as use_count() only counts other shared_ptr's.

    Does anyone know of a clean way to get the weak reference count or will
    I have to roll my own (which rather defeats the point of using weak+shared in the first place)?


    The whole point of a weak pointer is that it is non-owning. It doesn't
    own a share in the referenced object, and does not contribute to whether
    the shared object can or cannot be deleted. The shared pointer's
    control block should be deleted automatically when there are no more
    shared pointers or weak pointers referring to it.

    Can you give a little more detail of your setup? In particular, are
    your shared pointers created with make_shared or are the shared objects
    and their control blocks separate? When you talk about "shared pointers referenced by weak_ptr's", do you mean that the weak objects are created
    from the strong objects and thus reference the same shared objects and
    control block (rather than referencing the shared pointers themselves)?
    It is helpful if you are precise in what refers to what.

    AFAIK there is no API way to get a count of the weak pointers
    referencing a given control block, but I think that if you need such a
    count, you are doing something odd.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Mon Jun 1 15:57:56 2026
    On Mon, 1 Jun 2026 16:02:35 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 01/06/2026 12:19, boltar@caprica.universe wrote:
    I have a system that has a block of central shared_ptrs each of which is
    references by numerous weak_ptr's all over the place. However when there are >> no further weak_ptr's referencing the shared_ptr can be deleted. However >there
    seems to be no built in way in C++ to get the number of weak_ptr's
    referencing a shared as use_count() only counts other shared_ptr's.

    Does anyone know of a clean way to get the weak reference count or will
    I have to roll my own (which rather defeats the point of using weak+shared in

    the first place)?


    The whole point of a weak pointer is that it is non-owning. It doesn't
    own a share in the referenced object, and does not contribute to whether
    the shared object can or cannot be deleted. The shared pointer's
    control block should be deleted automatically when there are no more
    shared pointers or weak pointers referring to it.

    I should have pointed out the shared pointers can also be deleted regardless
    of whether any weak pointers are pointing to it, otherwise I'd just use
    shared pointers everywhere.

    Can you give a little more detail of your setup? In particular, are
    your shared pointers created with make_shared or are the shared objects
    and their control blocks separate? When you talk about "shared pointers

    Irrelevant AFAIK.

    referenced by weak_ptr's", do you mean that the weak objects are created >from the strong objects and thus reference the same shared objects and >control block (rather than referencing the shared pointers themselves)?
    It is helpful if you are precise in what refers to what.

    Huh?

    void myfunc(..., shared_ptri<st_object> &sp)
    {
    :
    weak_ptr<st_object> wp = sp
    :
    }

    etc.

    AFAIK there is no API way to get a count of the weak pointers
    referencing a given control block, but I think that if you need such a >count, you are doing something odd.

    Not really. I need to delete an object held in a shared pointer if:

    1) Its max lifetime has expired regardless of what references it.
    2) If nothing else is referencing it.

    Whichever comes sooner.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Mon Jun 1 19:34:36 2026
    On 01/06/2026 17:57, boltar@caprica.universe wrote:
    On Mon, 1 Jun 2026 16:02:35 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 01/06/2026 12:19, boltar@caprica.universe wrote:
    I have a system that has a block of central shared_ptrs each of which is >>> references by numerous weak_ptr's all over the place. However when
    there are
    no further weak_ptr's referencing the shared_ptr can be deleted. However
    there
    seems to be no built in way in C++ to get the number of weak_ptr's
    referencing a shared as use_count() only counts other shared_ptr's.

    Does anyone know of a clean way to get the weak reference count or will
    I have to roll my own (which rather defeats the point of using
    weak+shared in

    the first place)?


    The whole point of a weak pointer is that it is non-owning.˙ It
    doesn't own a share in the referenced object, and does not contribute
    to whether the shared object can or cannot be deleted.˙ The shared
    pointer's control block should be deleted automatically when there are
    no more shared pointers or weak pointers referring to it.

    I should have pointed out the shared pointers can also be deleted
    regardless
    of whether any weak pointers are pointing to it, otherwise I'd just use shared pointers everywhere.


    Please try to be careful with the terminology - it will help people
    understand your issues.

    Weak pointers do not point at shared pointers.

    There are four types of objects that are important here.

    1. The managed resource - the shared object that must exist until all referencing shared pointers are gone.

    2. The control blocks. These contain a pointer to the managed resource,
    a count of shared pointers, a count of weak pointers, and information to handle the deletion of the managed resource. It is the control block
    that manages the lifetime of the managed resource and its memory - /not/
    the shared pointers or weak pointers.

    3. Shared pointers. These contain a pointer to the managed resource (available with the "get()" method), and a pointer to the control block.

    4. Weak pointers. These also contain pointers to the managed resource
    and to the control block, but those pointers are not directly accessible
    until you use the "lock()" method to generate a shared pointer.

    Shared pointers and weak pointers are just local objects, typically in registers or on the stack. They are created and deleted individually,
    without affecting any other shared or weak pointers. But their creation
    and deletion atomically increments or decrements the matching count in
    the control block. If the control block's shared pointer reaches zero,
    the managed resource is destroyed. If the control block's shared
    pointer count plus weak pointer count reaches zero, the control block is destroyed.

    At no point does the deletion of weak pointers lead to the deletion of
    shared pointers. If there are no shared pointers left, then the
    deletion of the last weak pointer will automatically lead to the
    deletion of the control block.

    The managed resource is destructed when the last shared pointer is
    destructed. But the memory for the managed resource is not necessarily reclaimed at this time. If the control block and the managed resource
    were created together with "make_shared", then the memory will not be reclaimed (de-allocated) until the control block is destroyed when the
    last weak pointer is destroyed. Until that time, the memory space hangs
    about but there is no object in it - the weak pointer is "expired" even
    though the associated memory is still allocated. If the control block
    and managed resource were created separately, however, then the managed resource's memory will be reclaimed at its destruction.

    Your descriptions about the relations between these four objects have
    been muddled (weak pointers do not reference shared pointers, and shared pointers do not hold or own the managed resource). The managed resource
    is owned by and managed by a control block that is hidden behind the
    scenes. Weak pointers and shared pointers reference the control block. Understanding the indirection here makes it easier to see what is going on.

    Given all that, what are you wanting to delete? And are you only
    interested in properly destructing the managed resources, or do you need
    to reclaim the memory as soon as possible? (This is particularly
    relevant if you are using make_shared().) Do you need to reclaim the
    memory for the control blocks rapidly, or is it fine to let them die off naturally as the weak pointers are deleted?

    For me, or anyone else, to be able to give you helpful advice, we would
    need to know what you are doing and what is not working as you would like.


    Can you give a little more detail of your setup?˙ In particular, are
    your shared pointers created with make_shared or are the shared
    objects and their control blocks separate?˙ When you talk about
    "shared pointers

    Irrelevant AFAIK.

    referenced by weak_ptr's", do you mean that the weak objects are
    created from the strong objects and thus reference the same shared
    objects and control block (rather than referencing the shared pointers
    themselves)? It is helpful if you are precise in what refers to what.

    Huh?

    void myfunc(..., shared_ptri<st_object> &sp)
    {
    :
    ˙˙˙˙weak_ptr<st_object> wp = sp
    :
    }

    etc.

    AFAIK there is no API way to get a count of the weak pointers
    referencing a given control block, but I think that if you need such a
    count, you are doing something odd.

    Not really. I need to delete an object held in a shared pointer if:

    1) Its max lifetime has expired regardless of what references it.
    2) If nothing else is referencing it.

    Whichever comes sooner.

    This mention of "lifetime" is new, and may change things entirely -
    depending on your answers to the questions above. But again, weak
    references do not influence the lifetime of the managed object, only the lifetime of the control block. They /may/ influence the time of
    de-allocation of the managed object's memory, if you used make_shared().

    Your managed objects will be deleted precisely when there are no shared pointers referencing the control block that manages it. If you want to
    delete the managed object after a certain time, you need to be able to
    delete all the corresponding shared pointers at that time. Weak
    pointers can be left - they will not affect the lifetime of the shared pointer, only the lifetime of the control block.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Tue Jun 2 08:18:40 2026
    On Mon, 1 Jun 2026 19:34:36 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 01/06/2026 17:57, boltar@caprica.universe wrote:
    I should have pointed out the shared pointers can also be deleted
    regardless
    of whether any weak pointers are pointing to it, otherwise I'd just use
    shared pointers everywhere.


    Please try to be careful with the terminology - it will help people >understand your issues.

    Weak pointers do not point at shared pointers.

    Then what terminology would you use? Linked to? Associated with? Its just semantics.

    Also their implementation is irrelevant, its the behaviour that matters and
    I'm quite well aware of how they behave.

    tl;dr

    Your managed objects will be deleted precisely when there are no shared >pointers referencing the control block that manages it. If you want to

    No, really? Thanks for the heads up sherlock.

    I think I can assume you don't know the answer to my question.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Tue Jun 2 10:39:05 2026
    On 02/06/2026 10:18, boltar@caprica.universe wrote:
    On Mon, 1 Jun 2026 19:34:36 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 01/06/2026 17:57, boltar@caprica.universe wrote:
    I should have pointed out the shared pointers can also be deleted
    regardless
    of whether any weak pointers are pointing to it, otherwise I'd just use
    shared pointers everywhere.


    Please try to be careful with the terminology - it will help people
    understand your issues.

    Weak pointers do not point at shared pointers.

    Then what terminology would you use? Linked to? Associated with? Its just semantics.

    I would prefer to use the standard terminology, because it makes things clearer. "Semantics" is the meaning of the code - it is never "just semantics", semantics are critical if you are writing code and not poetry.

    I am trying to understand exactly what you are doing, so that I (or
    someone else) might be able to help you.


    Also their implementation is irrelevant, its the behaviour that matters and I'm quite well aware of how they behave.

    I agree that the implementation does not matter per se, but a brief description of the implementation can help understand the behaviour.


    tl;dr

    Your managed objects will be deleted precisely when there are no
    shared pointers referencing the control block that manages it.˙ If you
    want to

    No, really? Thanks for the heads up sherlock.
    I think I can assume you don't know the answer to my question.


    I don't know your question, because you haven't managed to express it.
    So I have to start by helping you explain what you are doing and what
    you want to achieve. If you don't want to do that, then you are just
    wasting everyone's time. After all, I presume you have already figured
    out there is no clean way to get the count of weak references in a
    control block, as it is not part of the API for shared or weak pointers.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Tue Jun 2 10:42:20 2026
    On Tue, 2 Jun 2026 10:39:05 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/06/2026 10:18, boltar@caprica.universe wrote:
    Then what terminology would you use? Linked to? Associated with? Its just
    semantics.

    I would prefer to use the standard terminology, because it makes things >clearer. "Semantics" is the meaning of the code - it is never "just >semantics", semantics are critical if you are writing code and not poetry.

    So what is the standard terminology in this case?

    No, really? Thanks for the heads up sherlock.
    I think I can assume you don't know the answer to my question.


    I don't know your question, because you haven't managed to express it.

    I laid it out in bullet points in a previous post. I'm not going over it
    again.

    wasting everyone's time. After all, I presume you have already figured
    out there is no clean way to get the count of weak references in a
    control block, as it is not part of the API for shared or weak pointers.

    Yes, that would annoyingly appear to be the case which is a shame. Internally there must be a 2 way link between weak_ptr and shared_ptr otherwise how can the weak ptr know when the shared ptr has gone? I don't imagine weak ptr does polling!

    If you didn't have a shared_ptr -> weak_ptr link you'd end up with
    the C problem of how do you know if a non null pointer is still pointing to valid memory therefor there must be a link therefore it should be possible
    to get from a shared ptr how many weak pointers reference it.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Tue Jun 2 13:32:58 2026
    On 02/06/2026 12:42, boltar@caprica.universe wrote:
    On Tue, 2 Jun 2026 10:39:05 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/06/2026 10:18, boltar@caprica.universe wrote:
    Then what terminology would you use? Linked to? Associated with? Its
    just
    semantics.

    I would prefer to use the standard terminology, because it makes
    things clearer.˙ "Semantics" is the meaning of the code - it is never
    "just semantics", semantics are critical if you are writing code and
    not poetry.

    So what is the standard terminology in this case?

    I gave that in a previous post.


    No, really? Thanks for the heads up sherlock.
    I think I can assume you don't know the answer to my question.


    I don't know your question, because you haven't managed to express it.

    I laid it out in bullet points in a previous post. I'm not going over it again.


    OK. If what you have written so far is all the information you are
    going to give, then it is not easy to give more help. I can explain
    more about the way these pointers work (which may also be of interest to others), but I can't help you directly.

    wasting everyone's time.˙ After all, I presume you have already
    figured out there is no clean way to get the count of weak references
    in a control block, as it is not part of the API for shared or weak
    pointers.

    Yes, that would annoyingly appear to be the case which is a shame.

    I don't see how it would help you. The control block is automatically destroyed when the last associated weak pointer is destroyed. It can't
    be destroyed before that. And it is unrelated to the destruction of the actual managed resource.

    Internally
    there must be a 2 way link between weak_ptr and shared_ptr otherwise how
    can
    the weak ptr know when the shared ptr has gone?

    There are no direct links between the weak pointers and the shared
    pointers. Each has a link to a common control block - and it is the
    control block that holds the count of shared pointers (and the count of
    weak pointers). The weak pointers know when there are no more shared
    pointers associated with the same control block because they can check
    the shared pointer count in the control block (usually through the
    "expired()" method, or by trying to get a shared pointer with "lock()").
    There are no links between shared pointers or weak pointers, or any
    links from the control block back to shared or weak pointers.

    I don't imagine weak ptr
    does
    polling!


    It "polls" (checks the control block) when you ask it by calling
    expired(), lock(), or use_count().

    If you didn't have a shared_ptr -> weak_ptr link you'd end up with the C problem of how do you know if a non null pointer is still pointing to
    valid memory therefor there must be a link therefore it should be possible
    to get from a shared ptr how many weak pointers reference it.


    That count information is held in the control block. Pointers to the
    managed resource (held by the control block and the shared and weak
    pointers) are valid as long as the shared pointer count in the control
    block is non-zero. Pointers to the control block (held by the shared
    and weak pointers) is valid as long as the sum of the shared pointer
    count and the weak pointer count in the control block is non-zero.
    Because these counters in the control block match the number of
    associated shared and weak pointers, if you have a shared pointer then
    using it to access the managed resource is valid. If you have a weak
    pointer or a shared pointer, using it to access the control block is
    valid. (Thus you cannot use a weak pointer to access the managed
    resource - you use it to try to create a shared pointer, then use that
    for the actual access.)




    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Tue Jun 2 14:52:46 2026
    On Tue, 2 Jun 2026 13:32:58 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/06/2026 12:42, boltar@caprica.universe wrote:
    I laid it out in bullet points in a previous post. I'm not going over it
    again.


    OK. If what you have written so far is all the information you are
    going to give, then it is not easy to give more help. I can explain

    Go back and read it again if its confusing you.

    Yes, that would annoyingly appear to be the case which is a shame.

    I don't see how it would help you. The control block is automatically >destroyed when the last associated weak pointer is destroyed. It can't
    be destroyed before that. And it is unrelated to the destruction of the >actual managed resource.

    So what? Whats preventing there being a shared ptr method that looks at the control block and tells you whether any weak ptrs are referencing it? I don't see the problem.

    Internally
    there must be a 2 way link between weak_ptr and shared_ptr otherwise how
    can
    the weak ptr know when the shared ptr has gone?

    There are no direct links between the weak pointers and the shared
    pointers. Each has a link to a common control block - and it is the
    control block that holds the count of shared pointers (and the count of
    weak pointers). The weak pointers know when there are no more shared

    Right, so if it has a count of the weak ptrs there's no reason the C++
    standard couldn't define a way to access it.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Tue Jun 2 17:14:36 2026
    On 02/06/2026 16:52, boltar@caprica.universe wrote:
    On Tue, 2 Jun 2026 13:32:58 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/06/2026 12:42, boltar@caprica.universe wrote:
    I laid it out in bullet points in a previous post. I'm not going over it >>> again.


    OK.˙ If what you have written so far is all the information you are
    going to give, then it is not easy to give more help.˙ I can explain

    Go back and read it again if its confusing you.

    Yes, that would annoyingly appear to be the case which is a shame.

    I don't see how it would help you.˙ The control block is automatically
    destroyed when the last associated weak pointer is destroyed.˙ It
    can't be destroyed before that.˙ And it is unrelated to the
    destruction of the actual managed resource.

    So what? Whats preventing there being a shared ptr method that looks at the control block and tells you whether any weak ptrs are referencing it? I don't
    see the problem.

    Internally
    there must be a 2 way link between weak_ptr and shared_ptr otherwise
    how can
    the weak ptr know when the shared ptr has gone?

    There are no direct links between the weak pointers and the shared
    pointers.˙ Each has a link to a common control block - and it is the
    control block that holds the count of shared pointers (and the count
    of weak pointers).˙ The weak pointers know when there are no more shared

    Right, so if it has a count of the weak ptrs there's no reason the C++ standard couldn't define a way to access it.


    That does not follow.

    Good APIs are small - they give access to the information that is needed
    to use the types effectively. They do not give access to internal
    information that might be held by the implementation. A different implementation of the same API might do things differently (though I
    can't think what). The methods of a type are based on presentations and discussions of use cases - it would seem that no one saw any use-cases
    for accessing a count of the weak pointers, or that the use-cases were
    not strong enough to overcome disadvantages of making the count visible.
    (I don't know of any such disadvantages, but I have not read the
    relevant proposal papers introducing weak pointers.)

    So while I cannot tell you specifically why the standard does not give
    access to this information, I certainly cannot agree with your leap of
    logic to claim there is no reason.

    And since you won't say why you want this information, or describe your
    actual problem, I guess you are stuck.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Tue Jun 2 15:31:57 2026
    On Tue, 2 Jun 2026 17:14:36 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/06/2026 16:52, boltar@caprica.universe wrote:
    Right, so if it has a count of the weak ptrs there's no reason the C++
    standard couldn't define a way to access it.


    That does not follow.

    Yes, it does.

    Good APIs are small - they give access to the information that is needed

    Well I need the info and its not provided.

    to use the types effectively. They do not give access to internal >information that might be held by the implementation. A different >implementation of the same API might do things differently (though I

    So what? I can provide the same info at the user level. I really have no
    idea what you're trying to argue here.

    can't think what). The methods of a type are based on presentations and >discussions of use cases - it would seem that no one saw any use-cases
    for accessing a count of the weak pointers, or that the use-cases were

    The C++ committees lack of judgement is a known issue.

    And since you won't say why you want this information, or describe your >actual problem, I guess you are stuck.

    I've already described it quite clearly as I keep telling you. If you don't know how or are unwilling to go back to a previous post thats your problem,
    not mine.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Tue Jun 2 15:34:27 2026
    On Tue, 2 Jun 2026 15:31:57 -0000 (UTC)
    boltar@caprica.universe gabbled:
    So what? I can provide the same info at the user level. I really have no

    Typo - "It can provide"


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Tue Jun 2 17:14:47 2026
    boltar@caprica.universe writes:
    On Tue, 2 Jun 2026 17:14:36 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/06/2026 16:52, boltar@caprica.universe wrote:
    Right, so if it has a count of the weak ptrs there's no reason the C++
    standard couldn't define a way to access it.


    That does not follow.

    Yes, it does.

    Good APIs are small - they give access to the information that is needed

    Well I need the info and its not provided.

    You haven't concisely presented _why_ you need the info, particularly
    since you claim to be able to track it yourself.


    to use the types effectively. They do not give access to internal >>information that might be held by the implementation. A different >>implementation of the same API might do things differently (though I

    So what? I can provide the same info at the user level. I really have no
    idea what you're trying to argue here.

    An implementation of the C++ standard library may not even keep
    a count of weak references to a shared object, if it can track
    the lifetime of the shared resource in a different way;
    the internals of the library should
    be opaque to the users of the library to provide the implementation
    flexibility to alter the underlying mechanism in the future if needed, without affecting the user-visible semantics of the operation.

    Clearly the Boost developers didn't feel that it was necessary to provide
    that information to the programmer.


    can't think what). The methods of a type are based on presentations and >>discussions of use cases - it would seem that no one saw any use-cases
    for accessing a count of the weak pointers, or that the use-cases were

    The C++ committees lack of judgement is a known issue.

    Is it? Note that shared_ptr came from Boost, so there were already
    extant implementations by the time it was standardized in C++11.

    And, just FYI, not all newsreaders make it easy to visit previously
    read articles.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Jun 2 13:21:01 2026
    On 6/1/2026 3:19 AM, boltar@caprica.universe wrote:
    I have a system that has a block of central shared_ptrs each of which is references by numerous weak_ptr's all over the place. However when there are no further weak_ptr's referencing the shared_ptr can be deleted. However there
    seems to be no built in way in C++ to get the number of weak_ptr's referencing a shared as use_count() only counts other shared_ptr's.

    Does anyone know of a clean way to get the weak reference count or will
    I have to roll my own (which rather defeats the point of using weak+shared in the first place)?


    I am not sure if there is a standard way to get the weak reference count directly from a shared_ptr, and that's by design, iirc. If they truly
    need to track weak observer count, they may need a custom control block
    or a wrapper.

    You are using multiple threads, right?

    Btw, have you ever heard about Joe Seighs atomic_ptr? Global and local
    counts using differential reference counting...

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Jun 2 13:24:42 2026
    On 6/2/2026 1:21 PM, Chris M. Thomasson wrote:
    On 6/1/2026 3:19 AM, boltar@caprica.universe wrote:
    I have a system that has a block of central shared_ptrs each of which is
    references by numerous weak_ptr's all over the place. However when
    there are
    no further weak_ptr's referencing the shared_ptr can be deleted.
    However there
    seems to be no built in way in C++ to get the number of weak_ptr's
    referencing a shared as use_count() only counts other shared_ptr's.

    Does anyone know of a clean way to get the weak reference count or will
    I have to roll my own (which rather defeats the point of using
    weak+shared in
    the first place)?


    I am not sure if there is a standard way to get the weak reference count directly from a shared_ptr, and that's by design, iirc. If they truly
    need to track weak observer count, they may need a custom control block
    or a wrapper.

    You are using multiple threads, right?

    Btw, have you ever heard about Joe Seighs atomic_ptr? Global and local counts using differential reference counting...

    Do you know the difference between basic atomic ref counting, and strong atomic ref counting? Well, differential ref counting can handle both...

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Tue Jun 2 13:26:49 2026
    On 6/2/2026 1:24 PM, Chris M. Thomasson wrote:
    Do you know the difference between basic atomic ref counting, and strong atomic ref counting? Well, differential ref counting can handle both...

    Btw, does C++ an intrusive ref counting? No need for a control block.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Wed Jun 3 08:47:15 2026
    On 02/06/2026 22:26, Chris M. Thomasson wrote:
    On 6/2/2026 1:24 PM, Chris M. Thomasson wrote:
    Do you know the difference between basic atomic ref counting, and
    strong atomic ref counting? Well, differential ref counting can handle
    both...

    Btw, does C++ an intrusive ref counting? No need for a control block.

    If you use "make_shared" to allocate and construct a new managed object,
    the control block and the managed object are allocated adjacently in one allocation. That is, in effect, intrusive reference counting.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Wed Jun 3 08:13:40 2026
    On Tue, 02 Jun 2026 17:14:47 GMT
    scott@slp53.sl.home (Scott Lurndal) gabbled:
    boltar@caprica.universe writes:
    On Tue, 2 Jun 2026 17:14:36 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 02/06/2026 16:52, boltar@caprica.universe wrote:
    Right, so if it has a count of the weak ptrs there's no reason the C++ >>>> standard couldn't define a way to access it.


    That does not follow.

    Yes, it does.

    Good APIs are small - they give access to the information that is needed

    Well I need the info and its not provided.

    You haven't concisely presented _why_ you need the info, particularly
    since you claim to be able to track it yourself.

    Yes I have and no I didn't. Other than that, spot on.

    So what? I can provide the same info at the user level. I really have no >>idea what you're trying to argue here.

    An implementation of the C++ standard library may not even keep
    a count of weak references to a shared object, if it can track

    Since it can't delete the control block until all the weak pointers have gone then it clearly does know.

    The C++ committees lack of judgement is a known issue.

    Is it? Note that shared_ptr came from Boost, so there were already
    extant implementations by the time it was standardized in C++11.

    So? A number of things were ported over from boost with useful changes.

    And, just FYI, not all newsreaders make it easy to visit previously
    read articles.

    Oh please, its 2026, not 1986.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Wed Jun 3 08:15:02 2026
    On Tue, 2 Jun 2026 13:21:01 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/1/2026 3:19 AM, boltar@caprica.universe wrote:
    Does anyone know of a clean way to get the weak reference count or will
    I have to roll my own (which rather defeats the point of using weak+shared in

    the first place)?


    I am not sure if there is a standard way to get the weak reference count >directly from a shared_ptr, and that's by design, iirc. If they truly
    need to track weak observer count, they may need a custom control block
    or a wrapper.

    You are using multiple threads, right?

    Nope, single threaded, buts that not a reason to revert to C pointers.

    Btw, have you ever heard about Joe Seighs atomic_ptr? Global and local >counts using differential reference counting...

    No I hadn't, thanks for the info, will google.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Jun 3 14:40:24 2026
    On 6/2/2026 11:47 PM, David Brown wrote:
    On 02/06/2026 22:26, Chris M. Thomasson wrote:
    On 6/2/2026 1:24 PM, Chris M. Thomasson wrote:
    Do you know the difference between basic atomic ref counting, and
    strong atomic ref counting? Well, differential ref counting can
    handle both...

    Btw, does C++ an intrusive ref counting? No need for a control block.

    If you use "make_shared" to allocate and construct a new managed object,
    the control block and the managed object are allocated adjacently in one allocation.˙ That is, in effect, intrusive reference counting.



    What about having an existing object say... foo:
    ___________
    struct per_object_ref
    {
    std::atomic<unsigned long> m_refs;
    };

    struct foo
    {
    per_object_ref m_per_object_ref;
    int m_bing;
    };
    ___________


    Then using CONTAINING_RECORD or the Linux equivalent container_of. There
    is absolutely no need for any external control block. If you want one,
    make one. Fair enough?

    Even std::make_shared is only effectively intrusive in terms of a single allocation trick?the layout is an implementation detail you don't
    control. Plus, you still can't get a shared_ptr from a raw this pointer without inheriting from std::enable_shared_from_this (which adds its own weak-count pointer overhead to the object layout anyway!).

    And don't forget: if a std::weak_ptr outlives the object, that whole
    single make_shared allocation stays pinned in memory, keeping your dead object's footprint hostage.

    With container_of, it's easy, explicit, and you just use offsetof. The
    object actually owns its destiny, alignment, and lifetime. :^) Am I
    wrong here?

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Jun 3 14:42:43 2026
    On 6/3/2026 1:15 AM, boltar@caprica.universe wrote:
    On Tue, 2 Jun 2026 13:21:01 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/1/2026 3:19 AM, boltar@caprica.universe wrote:
    Does anyone know of a clean way to get the weak reference count or will
    I have to roll my own (which rather defeats the point of using
    weak+shared in

    the first place)?


    I am not sure if there is a standard way to get the weak reference
    count directly from a shared_ptr, and that's by design, iirc. If they
    truly need to track weak observer count, they may need a custom
    control block or a wrapper.

    You are using multiple threads, right?

    Nope, single threaded, buts that not a reason to revert to C pointers.

    Why are you using std::shared_ptr on a single threaded system? Are you planning to go multi threaded? std::unique_ptr? Would that work for your system?


    Btw, have you ever heard about Joe Seighs atomic_ptr? Global and local
    counts using differential reference counting...

    No I hadn't, thanks for the info, will google.


    No reason to use Joes atomic_ptr if you are not using multiple threads.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Wed Jun 3 14:46:57 2026
    On 6/3/2026 2:42 PM, Chris M. Thomasson wrote:
    On 6/3/2026 1:15 AM, boltar@caprica.universe wrote:
    On Tue, 2 Jun 2026 13:21:01 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/1/2026 3:19 AM, boltar@caprica.universe wrote:
    Does anyone know of a clean way to get the weak reference count or will >>>> I have to roll my own (which rather defeats the point of using
    weak+shared in

    the first place)?


    I am not sure if there is a standard way to get the weak reference
    count directly from a shared_ptr, and that's by design, iirc. If they
    truly need to track weak observer count, they may need a custom
    control block or a wrapper.

    You are using multiple threads, right?

    Nope, single threaded, buts that not a reason to revert to C pointers.

    Why are you using std::shared_ptr on a single threaded system? Are you planning to go multi threaded? std::unique_ptr? Would that work for your system?

    Humm... Fwiw, perhaps a simple non-atomic intrusive ref count would give
    you everything shared_ptr does with zero atomic overhead and full
    visibility into your weak count, since you'd own the control block yourself.




    Btw, have you ever heard about Joe Seighs atomic_ptr? Global and
    local counts using differential reference counting...

    No I hadn't, thanks for the info, will google.


    No reason to use Joes atomic_ptr if you are not using multiple threads.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Thu Jun 4 08:26:42 2026
    On 03/06/2026 23:40, Chris M. Thomasson wrote:
    On 6/2/2026 11:47 PM, David Brown wrote:
    On 02/06/2026 22:26, Chris M. Thomasson wrote:
    On 6/2/2026 1:24 PM, Chris M. Thomasson wrote:
    Do you know the difference between basic atomic ref counting, and
    strong atomic ref counting? Well, differential ref counting can
    handle both...

    Btw, does C++ an intrusive ref counting? No need for a control block.

    If you use "make_shared" to allocate and construct a new managed
    object, the control block and the managed object are allocated
    adjacently in one allocation.˙ That is, in effect, intrusive reference
    counting.



    What about having an existing object say... foo:
    ___________
    struct per_object_ref
    {
    ˙˙˙ std::atomic<unsigned long> m_refs;
    };

    struct foo
    {
    ˙˙˙ per_object_ref m_per_object_ref;
    ˙˙˙ int m_bing;
    };
    ___________


    Then using CONTAINING_RECORD or the Linux equivalent container_of. There
    is absolutely no need for any external control block. If you want one,
    make one. Fair enough?

    Even std::make_shared is only effectively intrusive in terms of a single allocation trick?the layout is an implementation detail you don't
    control. Plus, you still can't get a shared_ptr from a raw this pointer without inheriting from std::enable_shared_from_this (which adds its own weak-count pointer overhead to the object layout anyway!).

    First, there is not going to be a big space difference between a "real" intrusive control block and a make_shared() one. In both cases, you are adding the reference counters. For the make_shared() control block, you
    also have a pointer to the actual object, while in a built-in intrusive solution you get that from a fixed offset.

    Second, a key point of the shared_ptr solution is that you can mix and
    match intrusive allocations and separate allocations. That does come
    with a cost - an extra pointer in the control block, and as you pointed
    out, if you want to be able to access the control block from the managed object you need to inherit from "enable_shared_from_this" and put a weak pointer in the object. These overheads are typically negligible
    compared to the managed object, but they are overheads.


    And don't forget: if a std::weak_ptr outlives the object, that whole
    single make_shared allocation stays pinned in memory, keeping your dead object's footprint hostage.


    Of course. If that is important to you, it is a reason for /not/ having intrusive pointers. When you make your object and the control block separately, only the control block remains allocated when all shared
    pointers are gone and only weak pointers remain.

    With container_of, it's easy, explicit, and you just use offsetof. The object actually owns its destiny, alignment, and lifetime. :^) Am I
    wrong here?

    Shared pointers are about /automatically/ managing destruction and
    storage deallocation, rather than having to do so explicitly and manually.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Thu Jun 4 11:00:03 2026
    Am 03.06.2026 um 23:42 schrieb Chris M. Thomasson:

    Why are you using std::shared_ptr on a single threaded system? ...
    You can use it to have reference counting. Incrementing the
    reference counter is only somewhat slower when done atomically.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Thu Jun 4 10:24:16 2026
    On Wed, 3 Jun 2026 14:42:43 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/3/2026 1:15 AM, boltar@caprica.universe wrote:
    On Tue, 2 Jun 2026 13:21:01 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/1/2026 3:19 AM, boltar@caprica.universe wrote:
    Does anyone know of a clean way to get the weak reference count or will >>>> I have to roll my own (which rather defeats the point of using
    weak+shared in

    the first place)?


    I am not sure if there is a standard way to get the weak reference
    count directly from a shared_ptr, and that's by design, iirc. If they
    truly need to track weak observer count, they may need a custom
    control block or a wrapper.

    You are using multiple threads, right?

    Nope, single threaded, buts that not a reason to revert to C pointers.

    Why are you using std::shared_ptr on a single threaded system? Are you >planning to go multi threaded? std::unique_ptr? Would that work for your >system?

    Are you seriously saying there's no use case for shared pointers in a single threaded system? Sure, we'll just have multiple copies of raw C pointers floating around and hope everyone searches the main list in their code all
    the time to check the pointer is still there and valid! Genius!

    As for unique_ptr - useless. I need multiple references to it and for [reasons] the C++ committee in their wisdom decided that weak_ptr couldn't be used with it.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Jun 4 13:31:50 2026
    On 6/4/2026 2:00 AM, Bonita Montero wrote:
    Am 03.06.2026 um 23:42 schrieb Chris M. Thomasson:

    Why are you using std::shared_ptr on a single threaded system? ...
    You can use it to have reference counting. Incrementing the
    reference counter is only somewhat slower when done atomically.


    std::shared_ptr has some baggage. For a single threaded program, I would
    never use it.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Thu Jun 4 13:41:33 2026
    On 6/4/2026 3:24 AM, boltar@caprica.universe wrote:
    On Wed, 3 Jun 2026 14:42:43 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/3/2026 1:15 AM, boltar@caprica.universe wrote:
    On Tue, 2 Jun 2026 13:21:01 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/1/2026 3:19 AM, boltar@caprica.universe wrote:
    Does anyone know of a clean way to get the weak reference count or
    will
    I have to roll my own (which rather defeats the point of using
    weak+shared in

    the first place)?


    I am not sure if there is a standard way to get the weak reference
    count directly from a shared_ptr, and that's by design, iirc. If
    they truly need to track weak observer count, they may need a custom
    control block or a wrapper.

    You are using multiple threads, right?

    Nope, single threaded, buts that not a reason to revert to C pointers.

    Why are you using std::shared_ptr on a single threaded system? Are you
    planning to go multi threaded? std::unique_ptr? Would that work for
    your system?

    Are you seriously saying there's no use case for shared pointers in a
    single
    threaded system? Sure, we'll just have multiple copies of raw C pointers floating around and hope everyone searches the main list in their code all the time to check the pointer is still there and valid! Genius!

    As for unique_ptr - useless. I need multiple references to it and for [reasons]
    the C++ committee in their wisdom decided that weak_ptr couldn't be used with
    it.



    Fair enough, shared ownership with weak observers is a legitimate single-threaded use case
    But you're paying for atomics you don't need... Every copy/destroy is an atomic RMW for no benefit
    An intrusive reference counter with a plain integer would give you the
    same semantics without the overhead
    And you could expose the weak count directly, solving your original
    problem too... ?

    A simple intrusive counter using a plain integer ? no atomics, no hidden control block, and you own the implementation so you can expose whatever counts you need.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Fri Jun 5 09:29:52 2026
    On Thu, 4 Jun 2026 13:41:33 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/4/2026 3:24 AM, boltar@caprica.universe wrote:
    Are you seriously saying there's no use case for shared pointers in a
    single
    threaded system? Sure, we'll just have multiple copies of raw C pointers
    floating around and hope everyone searches the main list in their code all >> the time to check the pointer is still there and valid! Genius!

    As for unique_ptr - useless. I need multiple references to it and for
    [reasons]
    the C++ committee in their wisdom decided that weak_ptr couldn't be used
    with
    it.



    Fair enough, shared ownership with weak observers is a legitimate >single-threaded use case
    But you're paying for atomics you don't need... Every copy/destroy is an >atomic RMW for no benefit

    The only copy/destroy is for the weak pointers. There is a single shared pointer
    for each object sitting in a global list which exists until the object is no longer required is either when there are no more observers or after a timeout regardless of observer number.

    An intrusive reference counter with a plain integer would give you the
    same semantics without the overhead

    Huh?

    A simple intrusive counter using a plain integer ? no atomics, no hidden >control block, and you own the implementation so you can expose whatever >counts you need.

    Not sure what you're suggesting. Yes I could roll my own class with counters and
    the C pointer embedded in it but whats the point? I can just use shared and weak

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Fri Jun 5 09:33:04 2026
    On Thu, 4 Jun 2026 13:31:50 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/4/2026 2:00 AM, Bonita Montero wrote:
    Am 03.06.2026 um 23:42 schrieb Chris M. Thomasson:

    Why are you using std::shared_ptr on a single threaded system? ...
    You can use it to have reference counting. Incrementing the
    reference counter is only somewhat slower when done atomically.


    std::shared_ptr has some baggage. For a single threaded program, I would >never use it.

    So what would you use in a large program worked on by a number of people that will all require references to the same group of objects that may be destroyed before their section of code gets called again (ie keeping local copies of
    C pointers or even an index into the main list is a no-no)?


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Fri Jun 5 11:42:19 2026
    Am 04.06.2026 um 22:31 schrieb Chris M. Thomasson:

    std::shared_ptr has some baggage.
    For a single threaded program, I would never use it.

    If you have a data structure where multiple object relate to
    another object on the heap a shared_ptr in each object is the
    most appropriate solution. The baggage is really minimal.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Jun 5 11:46:23 2026
    On 05/06/2026 11:33, boltar@caprica.universe wrote:
    On Thu, 4 Jun 2026 13:31:50 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/4/2026 2:00 AM, Bonita Montero wrote:
    Am 03.06.2026 um 23:42 schrieb Chris M. Thomasson:

    Why are you using std::shared_ptr on a single threaded system? ...
    You can use it to have reference counting. Incrementing the
    reference counter is only somewhat slower when done atomically.


    std::shared_ptr has some baggage. For a single threaded program, I
    would never use it.

    So what would you use in a large program worked on by a number of people that
    will all require references to the same group of objects that may be destroyed
    before their section of code gets called again (ie keeping local copies of
    C pointers or even an index into the main list is a no-no)?


    If you are on x86 and the managed objects are not insignificant in size,
    the overhead of shared_ptr is probably negligible. If you are targeting
    other types of processor, or have smaller objects, then it might be
    worth having a similar solution that does not need atomic counters (and possibly does not need both strong and weak pointers).


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Fri Jun 5 11:00:34 2026
    On Fri, 5 Jun 2026 11:46:23 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 05/06/2026 11:33, boltar@caprica.universe wrote:
    On Thu, 4 Jun 2026 13:31:50 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/4/2026 2:00 AM, Bonita Montero wrote:
    Am 03.06.2026 um 23:42 schrieb Chris M. Thomasson:

    Why are you using std::shared_ptr on a single threaded system? ...
    You can use it to have reference counting. Incrementing the
    reference counter is only somewhat slower when done atomically.


    std::shared_ptr has some baggage. For a single threaded program, I
    would never use it.

    So what would you use in a large program worked on by a number of people
    that
    will all require references to the same group of objects that may be
    destroyed
    before their section of code gets called again (ie keeping local copies of >> C pointers or even an index into the main list is a no-no)?


    If you are on x86 and the managed objects are not insignificant in size,
    the overhead of shared_ptr is probably negligible. If you are targeting >other types of processor, or have smaller objects, then it might be
    worth having a similar solution that does not need atomic counters (and >possibly does not need both strong and weak pointers).

    If you're talking embedded then I wouldn't be writing this sort of server
    in the first place.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Fri Jun 5 13:22:49 2026
    On 05/06/2026 13:00, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 11:46:23 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 05/06/2026 11:33, boltar@caprica.universe wrote:
    On Thu, 4 Jun 2026 13:31:50 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/4/2026 2:00 AM, Bonita Montero wrote:
    Am 03.06.2026 um 23:42 schrieb Chris M. Thomasson:

    Why are you using std::shared_ptr on a single threaded system? ...
    You can use it to have reference counting. Incrementing the
    reference counter is only somewhat slower when done atomically.


    std::shared_ptr has some baggage. For a single threaded program, I
    would never use it.

    So what would you use in a large program worked on by a number of
    people that
    will all require references to the same group of objects that may be
    destroyed
    before their section of code gets called again (ie keeping local
    copies of
    C pointers or even an index into the main list is a no-no)?


    If you are on x86 and the managed objects are not insignificant in
    size, the overhead of shared_ptr is probably negligible.˙ If you are
    targeting other types of processor, or have smaller objects, then it
    might be worth having a similar solution that does not need atomic
    counters (and possibly does not need both strong and weak pointers).

    If you're talking embedded then I wouldn't be writing this sort of server
    in the first place.


    First, I have no idea what your software is and what kind of a system it
    is running on, other than what you have said - it is large,
    single-threaded, has lots of developers and is using shared and weak
    pointers.

    Secondly, "embedded" does not necessarily mean small - the there are
    lots of big embedded systems.

    Thirdly, "servers" does not necessarily mean x86 - there are lots of ARM servers.

    So while it might be reasonable to guess that it's more likely you are targeting x86 than anything else, it is not a clear implication of
    anything you have written up to now. And if it did turn out that you
    were using something other than x86 (or might do so in the future), the overhead of sequentially consistent atomics can be a good deal higher on
    many other processors, making it a relevant consideration as they are unnecessary for single-threaded work.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Fri Jun 5 15:14:39 2026
    Am 05.06.2026 um 13:00 schrieb boltar@caprica.universe:

    If you're talking embedded then I wouldn't be writing this sort of server
    in the first place.

    If you have an embedded system that is that constrained there's not
    much benefit of C++ vs. C. But usually you've got the resources to
    do sth. like having shared_ptr<>s.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Scott Lurndal@3:633/10 to All on Fri Jun 5 14:13:31 2026
    Bonita Montero <Bonita.Montero@gmail.com> writes:
    Am 05.06.2026 um 13:00 schrieb boltar@caprica.universe:

    If you're talking embedded then I wouldn't be writing this sort of server
    in the first place.

    If you have an embedded system that is that constrained there's not
    much benefit of C++ vs. C.

    That statement is incorrect.

    C with classes is a viable subset of C++ that can be used in
    any constrained environment (e.g. embedded with small memory)
    or when developing low-level high performance code like operating
    systems and hypervisors.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Fri Jun 5 14:45:15 2026
    On Fri, 5 Jun 2026 13:22:49 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 05/06/2026 13:00, boltar@caprica.universe wrote:
    If you're talking embedded then I wouldn't be writing this sort of server
    in the first place.


    First, I have no idea what your software is and what kind of a system it
    is running on, other than what you have said - it is large,
    single-threaded, has lots of developers and is using shared and weak >pointers.

    Secondly, "embedded" does not necessarily mean small - the there are
    lots of big embedded systems.

    Thirdly, "servers" does not necessarily mean x86 - there are lots of ARM >servers.

    I'm talking about server in the software sense, not hardware. Given I'm not using emebedded assembler I couldn't care less what the CPU is.

    anything you have written up to now. And if it did turn out that you
    were using something other than x86 (or might do so in the future), the

    You do realise Macs have been using ARM for years now?

    overhead of sequentially consistent atomics can be a good deal higher on >many other processors, making it a relevant consideration as they are >unnecessary for single-threaded work.

    Feel free to show how to link C pointers with atomics to do pointer
    reference counting because I have better things to do.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Paavo Helde@3:633/10 to All on Fri Jun 5 18:27:52 2026
    On 6/4/2026 11:41 PM, Chris M. Thomasson wrote:

    But you're paying for atomics you don't need... Every copy/destroy is an atomic RMW for no benefit
    An intrusive reference counter with a plain integer would give you the
    same semantics without the overhead

    I also used to think the same in the past. Then I ran some experiments
    and when I discovered that the supposed overhead from atomic refcounter
    (as compared to a non-atomic refcounter) was not even measurable, I
    changed my mind.

    This was on x86_64, in a multi-threaded app, but obviously all objects
    were accessed only by a single thread at any given time (otherwise one
    could not use non-atomic counters in the first place). YMMV.

    Anyway, I'm sure if there were any mentionable benefits to be gained
    from having a single-threaded shared pointer, it would be present in the
    C++ standard by now.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Fri Jun 5 15:56:15 2026
    On Fri, 5 Jun 2026 18:27:52 +0300
    Paavo Helde <eesnimi@osa.pri.ee> gabbled:
    On 6/4/2026 11:41 PM, Chris M. Thomasson wrote:

    But you're paying for atomics you don't need... Every copy/destroy is an
    atomic RMW for no benefit
    An intrusive reference counter with a plain integer would give you the
    same semantics without the overhead

    I also used to think the same in the past. Then I ran some experiments
    and when I discovered that the supposed overhead from atomic refcounter
    (as compared to a non-atomic refcounter) was not even measurable, I
    changed my mind.

    This was on x86_64, in a multi-threaded app, but obviously all objects
    were accessed only by a single thread at any given time (otherwise one
    could not use non-atomic counters in the first place). YMMV.

    Anyway, I'm sure if there were any mentionable benefits to be gained
    from having a single-threaded shared pointer, it would be present in the
    C++ standard by now.

    There seem to be a lot of people on this group who don't seem to understand the purpose of shared pointers. Its not to make life easier when multi threading.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Fri Jun 5 19:51:39 2026
    Am 05.06.2026 um 16:13 schrieb Scott Lurndal:

    C with classes is a viable subset of C++ that can be used in
    any constrained environment (e.g. embedded with small memory)
    or when developing low-level high performance code like operating
    systems and hypervisors.

    When you have embedded with small memory the advantage of C++
    is negligible.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Fri Jun 5 12:53:27 2026
    On 6/5/2026 2:42 AM, Bonita Montero wrote:
    Am 04.06.2026 um 22:31 schrieb Chris M. Thomasson:

    std::shared_ptr has some baggage.
    For a single threaded program, I would never use it.

    If you have a data structure where multiple object relate to
    another object on the heap a shared_ptr in each object is the
    most appropriate solution. The baggage is really minimal.



    For single thread why not just use a non-atomic intrusive count? You can
    make all the pointers you want.

    Side note: Unnecessary atomic RMWs and memory barriers on every
    copy/destroy in a single-threaded context is real overhead, not minimal.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Fri Jun 5 13:02:26 2026
    On 6/5/2026 8:56 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 18:27:52 +0300
    Paavo Helde <eesnimi@osa.pri.ee> gabbled:
    On 6/4/2026 11:41 PM, Chris M. Thomasson wrote:

    But you're paying for atomics you don't need... Every copy/destroy is
    an atomic RMW for no benefit
    An intrusive reference counter with a plain integer would give you
    the same semantics without the overhead

    I also used to think the same in the past. Then I ran some experiments
    and when I discovered that the supposed overhead from atomic
    refcounter (as compared to a non-atomic refcounter) was not even
    measurable, I changed my mind.

    This was on x86_64, in a multi-threaded app, but obviously all objects
    were accessed only by a single thread at any given time (otherwise one
    could not use non-atomic counters in the first place). YMMV.

    Anyway, I'm sure if there were any mentionable benefits to be gained
    from having a single-threaded shared pointer, it would be present in
    the C++ standard by now.

    There seem to be a lot of people on this group who don't seem to
    understand the purpose of shared pointers. Its not to make life easier
    when multi
    threading.

    So, well, have you considered the non-atomic intrusive counter approach
    for your use case?

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Fri Jun 5 13:11:53 2026
    On 6/5/2026 8:27 AM, Paavo Helde wrote:
    On 6/4/2026 11:41 PM, Chris M. Thomasson wrote:

    But you're paying for atomics you don't need... Every copy/destroy is
    an atomic RMW for no benefit
    An intrusive reference counter with a plain integer would give you the
    same semantics without the overhead

    I also used to think the same in the past. Then I ran some experiments
    and when I discovered that the supposed overhead from atomic refcounter
    (as compared to a non-atomic refcounter) was not even measurable, I
    changed my mind.

    This was on x86_64, in a multi-threaded app, but obviously all objects
    were accessed only by a single thread at any given time (otherwise one
    could not use non-atomic counters in the first place). YMMV.

    Anyway, I'm sure if there were any mentionable benefits to be gained
    from having a single-threaded shared pointer, it would be present in the
    C++ standard by now.



    x86 TSO makes your benchmark result "almost" meaningless in a single
    threaded case as a general claim. Try it on ARM with real barriers and
    report back. Also, there is a thread over on comp.arch:

    comp.arch: ARM CAS vs LL/SC

    ______________
    Paul Clayton <paaronclayton@gmail.com> writes:
    I seem to recall reading that x86's LOCK instructions take
    hundreds of cycles. While some of this is probably from stronger
    memory ordering guarantees, I get the impression that the
    operation itself is not aggressively optimized.

    Let's see:

    variable x 1 x !
    variable y -1 y !

    : bench-!@
    1 5000000 0 do x !@ y !@ loop drop ;

    : bench-atomic!@
    1 5000000 0 do x atomic!@ y atomic!@ loop drop ;


    : bench-+!@
    1 5000000 0 do x +!@ y +!@ loop drop ;

    : bench-atomic+!@
    1 5000000 0 do x atomic+!@ y atomic+!@ loop drop ;

    On a Ryzen 8700G (Zen4) each execution of a !@ (exchange) or +!@ (fetch-and-add) costs the following numbers of cycles (including
    overhead):

    !@ +!@
    7.5 7.3 not atomic
    14.2 13.2 atomic

    On a Xeon E-2388G (Rocket Lake):

    !@ +!@
    8.5 7.1 not atomic
    25.8 26.6 atomic

    - anton
    ______________


    Any thoughts?

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Fri Jun 5 13:58:07 2026
    On 6/5/2026 7:45 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 13:22:49 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 05/06/2026 13:00, boltar@caprica.universe wrote:
    If you're talking embedded then I wouldn't be writing this sort of
    server
    in the first place.


    First, I have no idea what your software is and what kind of a system
    it is running on, other than what you have said - it is large, single-
    threaded, has lots of developers and is using shared and weak pointers.

    Secondly, "embedded" does not necessarily mean small - the there are
    lots of big embedded systems.

    Thirdly, "servers" does not necessarily mean x86 - there are lots of
    ARM servers.

    I'm talking about server in the software sense, not hardware. Given I'm not using emebedded assembler I couldn't care less what the CPU is.

    anything you have written up to now.˙ And if it did turn out that you
    were using something other than x86 (or might do so in the future), the

    You do realise Macs have been using ARM for years now?

    overhead of sequentially consistent atomics can be a good deal higher
    on many other processors, making it a relevant consideration as they
    are unnecessary for single-threaded work.

    Feel free to show how to link C pointers with atomics to do pointer
    reference counting because I have better things to do.


    wrt strong atomic reference counting, look at my post in comp.lang.c++:
    'Some history wrt reference counting...'. basic atomic ref counting is different from strong. std::shared_ptr is basic. If you are truly single-threaded forever, the atomics are just waste, imvvho that is. But
    you mentioned a server... Well, what kind of load are you expecting? Are
    you thinking about scalability down the road?



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Fri Jun 5 14:00:03 2026
    On 6/5/2026 10:51 AM, Bonita Montero wrote:
    Am 05.06.2026 um 16:13 schrieb Scott Lurndal:

    C with classes is a viable subset of C++ that can be used in
    any constrained environment (e.g. embedded with small memory)
    or when developing low-level high performance code like operating
    systems and hypervisors.

    When you have embedded with small memory the advantage of C++
    is negligible.

    We can use POD, constexpr, inline functions, zero-overhead templates...
    C++ has a perfectly viable low-overhead subset for constrained
    environments. The baggage is opt-in.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Sat Jun 6 10:51:19 2026
    Am 05.06.2026 um 23:00 schrieb Chris M. Thomasson:
    On 6/5/2026 10:51 AM, Bonita Montero wrote:
    Am 05.06.2026 um 16:13 schrieb Scott Lurndal:

    C with classes is a viable subset of C++ that can be used in
    any constrained environment (e.g. embedded with small memory)
    or when developing low-level high performance code like operating
    systems and hypervisors.

    When you have embedded with small memory the advantage of C++
    is negligible.

    We can use POD, constexpr, inline functions, zero-overhead templates...
    C++ has a perfectly viable low-overhead subset for constrained
    environments. The baggage is opt-in.

    A LOCK XADD is < 10 clock cycles on current CPUs.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Sat Jun 6 10:51:50 2026
    Am 05.06.2026 um 21:53 schrieb Chris M. Thomasson:

    Side note: Unnecessary atomic RMWs and memory barriers on every copy/ destroy in a single-threaded context is real overhead, not minimal.

    A LOCK XADD is < 10 clock cycles on current CPUs.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Sat Jun 6 10:45:09 2026
    On Fri, 5 Jun 2026 13:02:26 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/5/2026 8:56 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 18:27:52 +0300
    Paavo Helde <eesnimi@osa.pri.ee> gabbled:
    On 6/4/2026 11:41 PM, Chris M. Thomasson wrote:

    But you're paying for atomics you don't need... Every copy/destroy is >>>> an atomic RMW for no benefit
    An intrusive reference counter with a plain integer would give you
    the same semantics without the overhead

    I also used to think the same in the past. Then I ran some experiments
    and when I discovered that the supposed overhead from atomic
    refcounter (as compared to a non-atomic refcounter) was not even
    measurable, I changed my mind.

    This was on x86_64, in a multi-threaded app, but obviously all objects
    were accessed only by a single thread at any given time (otherwise one
    could not use non-atomic counters in the first place). YMMV.

    Anyway, I'm sure if there were any mentionable benefits to be gained
    from having a single-threaded shared pointer, it would be present in
    the C++ standard by now.

    There seem to be a lot of people on this group who don't seem to
    understand the purpose of shared pointers. Its not to make life easier
    when multi
    threading.

    So, well, have you considered the non-atomic intrusive counter approach
    for your use case?

    Provide some example code, I have better things to do than figure it out.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Sat Jun 6 10:46:00 2026
    On Fri, 5 Jun 2026 13:58:07 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/5/2026 7:45 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 13:22:49 +0200
    Feel free to show how to link C pointers with atomics to do pointer
    reference counting because I have better things to do.


    wrt strong atomic reference counting, look at my post in comp.lang.c++: >'Some history wrt reference counting...'. basic atomic ref counting is >different from strong. std::shared_ptr is basic. If you are truly >single-threaded forever, the atomics are just waste, imvvho that is. But
    you mentioned a server... Well, what kind of load are you expecting? Are
    you thinking about scalability down the road?

    Not a huge load hence single threaded, but lots of objects.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sat Jun 6 20:59:03 2026
    On 05/06/2026 16:45, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 13:22:49 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 05/06/2026 13:00, boltar@caprica.universe wrote:
    If you're talking embedded then I wouldn't be writing this sort of
    server
    in the first place.


    First, I have no idea what your software is and what kind of a system
    it is running on, other than what you have said - it is large, single-
    threaded, has lots of developers and is using shared and weak pointers.

    Secondly, "embedded" does not necessarily mean small - the there are
    lots of big embedded systems.

    Thirdly, "servers" does not necessarily mean x86 - there are lots of
    ARM servers.

    I'm talking about server in the software sense, not hardware.

    In the software sense, you can have "servers" on all sorts of things. I
    have servers on small embedded systems - though I appreciate that
    requiring many developers implies that your server software is a program
    of significant size.

    Given I'm not
    using emebedded assembler I couldn't care less what the CPU is.

    It usually doesn't matter too much. But sometimes there can be
    differences that are important enough to affect strategies you use in
    coding, in the parts that are used often enough to warrant extra care in efficiency.


    anything you have written up to now.˙ And if it did turn out that you
    were using something other than x86 (or might do so in the future), the

    You do realise Macs have been using ARM for years now?

    Yes.


    overhead of sequentially consistent atomics can be a good deal higher
    on many other processors, making it a relevant consideration as they
    are unnecessary for single-threaded work.

    Feel free to show how to link C pointers with atomics to do pointer
    reference counting because I have better things to do.


    Have you a rough idea how the shared pointers you are using now are implemented? An implementation that does not use atomics, because
    atomic accesses are unnecessary overhead on a single-threaded system,
    are the same - except the reference counts are normal integer types, not atomic types.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sat Jun 6 21:05:27 2026
    On 05/06/2026 16:13, Scott Lurndal wrote:
    Bonita Montero <Bonita.Montero@gmail.com> writes:
    Am 05.06.2026 um 13:00 schrieb boltar@caprica.universe:

    If you're talking embedded then I wouldn't be writing this sort of server >>> in the first place.

    If you have an embedded system that is that constrained there's not
    much benefit of C++ vs. C.

    That statement is incorrect.

    Given the person who made the statement, it's not a surprise.

    (I work with constrained embedded systems, and see significant benefit
    in using C++ rather than C.)


    C with classes is a viable subset of C++ that can be used in
    any constrained environment (e.g. embedded with small memory)
    or when developing low-level high performance code like operating
    systems and hypervisors.

    That is true.

    In my work, I use a much larger subset of C++ than that. There are key
    areas that I avoid (anything involving dynamic memory allocation, and exceptions), but other than that I use a lot of C++ features - classes, namespaces, templates, overloaded functions, overloaded operators, some standard classes (like variant with visitors, optional, array,
    lock_guard), lambdas, concepts, type inference (though with C23 I can do
    that in C now), and constexpr.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sat Jun 6 12:54:07 2026
    On 6/6/2026 3:46 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 13:58:07 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/5/2026 7:45 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 13:22:49 +0200
    Feel free to show how to link C pointers with atomics to do pointer
    reference counting because I have better things to do.


    wrt strong atomic reference counting, look at my post in comp.lang.c+
    +: 'Some history wrt reference counting...'.˙ basic atomic ref
    counting is different from strong. std::shared_ptr is basic. If you
    are truly single-threaded forever, the atomics are just waste, imvvho
    that is. But you mentioned a server... Well, what kind of load are you
    expecting? Are you thinking about scalability down the road?

    Not a huge load hence single threaded, but lots of objects.


    Well, is it always going to be that way? If so, my advice is to use what
    you have already working if not perhaps "highly _efficient_", who cares
    its not meant to be a scalable server anyway? Or, perhaps ponder on
    using intrusive non-atomic ref counters should improve performance in
    your single thread server anyway?

    A lot of objects? std::shared_ptr might not be the best for that alone.
    Can you sketch out your usage pattern in general using structs?

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sat Jun 6 12:57:46 2026
    On 6/6/2026 1:51 AM, Bonita Montero wrote:
    Am 05.06.2026 um 23:00 schrieb Chris M. Thomasson:
    On 6/5/2026 10:51 AM, Bonita Montero wrote:
    Am 05.06.2026 um 16:13 schrieb Scott Lurndal:

    C with classes is a viable subset of C++ that can be used in
    any constrained environment (e.g. embedded with small memory)
    or when developing low-level high performance code like operating
    systems and hypervisors.

    When you have embedded with small memory the advantage of C++
    is negligible.

    We can use POD, constexpr, inline functions, zero-overhead
    templates... C++ has a perfectly viable low-overhead subset for
    constrained environments. The baggage is opt-in.

    A LOCK XADD is < 10 clock cycles on current CPUs.

    You are missing the point... Why use it anyway on a single threaded
    system anyway? What is a LOCK XADD vs a RMW without any LOCK, or just in
    code wrt a non-atomic RMW (aka, say, ++count)? If there is no second
    thread, there is no one to synchronize with. Paying a synchronization
    tax when there is no concurrency is just bad engineering!

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sat Jun 6 13:07:05 2026
    On 6/6/2026 1:51 AM, Bonita Montero wrote:
    Am 05.06.2026 um 21:53 schrieb Chris M. Thomasson:

    Side note: Unnecessary atomic RMWs and memory barriers on every copy/
    destroy in a single-threaded context is real overhead, not minimal.

    A LOCK XADD is < 10 clock cycles on current CPUs.

    You are missing the point...? Why use it on a single threaded system
    anyway? What is a LOCK XADD vs a RMW without any LOCK, or just in code
    wrt a non-atomic RMW (++count)? If there is no second thread, there is
    no one to synchronize with. Paying a synchronization tax when there is
    no concurrency is just bad engineering?



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sat Jun 6 13:11:52 2026
    On 6/6/2026 3:45 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 13:02:26 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/5/2026 8:56 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 18:27:52 +0300
    Paavo Helde <eesnimi@osa.pri.ee> gabbled:
    On 6/4/2026 11:41 PM, Chris M. Thomasson wrote:

    But you're paying for atomics you don't need... Every copy/destroy
    is an atomic RMW for no benefit
    An intrusive reference counter with a plain integer would give you
    the same semantics without the overhead

    I also used to think the same in the past. Then I ran some
    experiments and when I discovered that the supposed overhead from
    atomic refcounter (as compared to a non-atomic refcounter) was not
    even measurable, I changed my mind.

    This was on x86_64, in a multi-threaded app, but obviously all
    objects were accessed only by a single thread at any given time
    (otherwise one could not use non-atomic counters in the first
    place). YMMV.

    Anyway, I'm sure if there were any mentionable benefits to be gained
    from having a single-threaded shared pointer, it would be present in
    the C++ standard by now.

    There seem to be a lot of people on this group who don't seem to
    understand the purpose of shared pointers. Its not to make life
    easier when multi
    threading.

    So, well, have you considered the non-atomic intrusive counter
    approach for your use case?

    Provide some example code, I have better things to do than figure it out.


    Ask and you shall receive. Here is the actual header infrastructure I
    use in my own engine for tracking resources. It includes both the lean intrusive smart pointer handle (iptr) and an asset database container
    template to show how handles move cleanly around a resource map.

    Notice that there are zero atomic instructions, zero separate control
    blocks on the heap, and perfect cache locality because the ref count
    lives inside the base layout of the asset itself. Its not perfect at
    all, just enough for me:
    ____________________________________
    #pragma once

    #include <map>
    #include <string>
    #include <fstream>
    #include <stdexcept>
    #include <cassert>

    namespace ct {
    namespace utils {

    static unsigned long g_utils_ctors = 0;
    static unsigned long g_utils_dtors = 0;


    struct iptr_base {
    mutable long m_iptr_base_count;
    iptr_base() : m_iptr_base_count(0) { ++g_utils_ctors; }
    virtual ~iptr_base() { ++g_utils_dtors; }
    };

    template<typename T>
    struct iptr {
    T* m_ptr;
    iptr(T* ptr = nullptr) : m_ptr(ptr) { if (m_ptr) m_ptr->m_iptr_base_count++; }
    iptr(iptr const& rhs) : m_ptr(rhs.m_ptr) { if (m_ptr) m_ptr->m_iptr_base_count++; }
    ~iptr() { sub_ref(); }

    void sub_ref() {
    if (m_ptr) {
    if (--m_ptr->m_iptr_base_count <= 0) {
    delete m_ptr;
    m_ptr = nullptr;
    }
    }
    }

    iptr& operator =(iptr const& rhs) {
    if (m_ptr == rhs.m_ptr) return *this;
    if (rhs.m_ptr) rhs.m_ptr->m_iptr_base_count++;
    sub_ref();
    m_ptr = rhs.m_ptr;
    return *this;
    }

    T* operator ->() { assert(m_ptr); return m_ptr; }
    T const* operator ->() const { assert(m_ptr); return m_ptr; }
    };

    // Add the database template to utils namespace
    template<typename T>
    struct iptr_database {
    std::map<std::string, T> m_database;

    T insert_unique(std::string const& name, T ptr) {
    if (!exists(name)) {
    m_database.insert({ name, ptr });
    return ptr;
    }
    return lookup(name);
    }

    T insert(std::string const& name, T ptr) {
    if (exists(name)) throw std::runtime_error("Key already exists: " + name);
    m_database.insert({ name, ptr });
    return ptr;
    }

    bool exists(std::string const& name) const {
    auto f = m_database.find(name);
    return f != m_database.end();
    }

    T lookup(std::string const& name) const {
    auto f = m_database.find(name);
    if (f == m_database.end())
    throw std::runtime_error("Key not found: " + name);
    return f->second;
    }

    };

    inline std::string file_load(std::string const& file_name) {
    std::ifstream file(file_name, std::ios::in |
    std::ios::binary | std::ios::ate);
    if (!file.is_open()) throw std::runtime_error("File not
    found: " + file_name);
    std::streamsize size = file.tellg();
    file.seekg(0, std::ios::beg);
    std::string buf;
    buf.resize(static_cast<size_t>(size));
    file.read(&buf[0], size);
    return buf;
    }
    }
    }
    ____________________________________

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sat Jun 6 13:17:55 2026
    On 6/6/2026 1:11 PM, Chris M. Thomasson wrote:
    On 6/6/2026 3:45 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 13:02:26 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/5/2026 8:56 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 18:27:52 +0300
    Paavo Helde <eesnimi@osa.pri.ee> gabbled:
    On 6/4/2026 11:41 PM, Chris M. Thomasson wrote:

    But you're paying for atomics you don't need... Every copy/destroy >>>>>> is an atomic RMW for no benefit
    An intrusive reference counter with a plain integer would give you >>>>>> the same semantics without the overhead

    I also used to think the same in the past. Then I ran some
    experiments and when I discovered that the supposed overhead from
    atomic refcounter (as compared to a non-atomic refcounter) was not
    even measurable, I changed my mind.

    This was on x86_64, in a multi-threaded app, but obviously all
    objects were accessed only by a single thread at any given time
    (otherwise one could not use non-atomic counters in the first
    place). YMMV.

    Anyway, I'm sure if there were any mentionable benefits to be
    gained from having a single-threaded shared pointer, it would be
    present in the C++ standard by now.

    There seem to be a lot of people on this group who don't seem to
    understand the purpose of shared pointers. Its not to make life
    easier when multi
    threading.

    So, well, have you considered the non-atomic intrusive counter
    approach for your use case?

    Provide some example code, I have better things to do than figure it out.


    Ask and you shall receive. Here is the actual header infrastructure I
    use in my own engine for tracking resources. It includes both the lean intrusive smart pointer handle (iptr) and an asset database container template to show how handles move cleanly around a resource map.

    Notice that there are zero atomic instructions, zero separate control
    blocks on the heap, and perfect cache locality because the ref count
    lives inside the base layout of the asset itself. Its not perfect at
    all, just enough for me:

    A weak pointer... ;^D iptr<T> const& ptr ?

    [...]

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sat Jun 6 20:08:14 2026
    On 6/6/2026 1:11 PM, Chris M. Thomasson wrote:
    On 6/6/2026 3:45 AM, boltar@caprica.universe wrote:
    On Fri, 5 Jun 2026 13:02:26 -0700
    [...]
    Provide some example code, I have better things to do than figure it out.


    Ask and you shall receive. Here is the actual header infrastructure I
    use in my own engine for tracking resources. It includes both the lean intrusive smart pointer handle (iptr) and an asset database container template to show how handles move cleanly around a resource map.

    Notice that there are zero atomic instructions, zero separate control
    blocks on the heap, and perfect cache locality because the ref count
    lives inside the base layout of the asset itself. Its not perfect at
    all, just enough for me:
    Fwiw, I use it for my graphics engine. The little database allows me to
    keep my loaded textures, models ect... unique. I do not want to load the
    same texture/model twice. Btw, I should point out that my system uses no cycles, so my iptr is fine for that. If you have cycles in your data,
    drop in:
    ____________
    iptr& operator =(iptr const& rhs)
    {
    if (rhs.m_ptr)
    {
    rhs.m_ptr->m_iptr_base_count++;
    }

    T* old_ptr = m_ptr;
    m_ptr = rhs.m_ptr;

    if (old_ptr)
    {
    if (--old_ptr->m_iptr_base_count <= 0)
    {
    delete old_ptr;
    }
    }

    return *this;
    }
    ____________

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Sun Jun 7 07:28:03 2026
    Am 06.06.2026 um 21:57 schrieb Chris M. Thomasson:

    You are missing the point... Why use it anyway on a single threaded
    system anyway? What is a LOCK XADD vs a RMW without any LOCK, or just
    in code wrt a non-atomic RMW (aka, say, ++count)? If there is no second thread, there is no one to synchronize with. Paying a synchronization
    tax when there is no concurrency is just bad engineering!

    If the price is small it's not bad engineering.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Sun Jun 7 07:28:32 2026
    Am 06.06.2026 um 22:07 schrieb Chris M. Thomasson:

    You are missing the point...? Why use it on a single threaded system
    anyway? What is a LOCK XADD vs a RMW without any LOCK, or just in code
    wrt a non-atomic RMW (++count)? If there is no second thread, there is
    no one to synchronize with. Paying a synchronization tax when there is
    no concurrency is just bad engineering?
    If the price is small it's not bad engineering.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Sun Jun 7 14:40:22 2026
    On Sat, 6 Jun 2026 20:59:03 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 05/06/2026 16:45, boltar@caprica.universe wrote:
    I'm talking about server in the software sense, not hardware.

    In the software sense, you can have "servers" on all sorts of things. I

    I guess it depends how you define server. I define it as something that has other programs connecting to it via sockets, pipes, whatever. Its not a
    daemon that just sits there doing its own thing.

    Feel free to show how to link C pointers with atomics to do pointer
    reference counting because I have better things to do.


    Have you a rough idea how the shared pointers you are using now are >implemented? An implementation that does not use atomics, because
    atomic accesses are unnecessary overhead on a single-threaded system,
    are the same - except the reference counts are normal integer types, not >atomic types.

    Atomics are great if all you need to do is a single uninterruptable
    operation. They're useless if you need to do a whole load of things in sequence which I suspect shared ptr has to do and in which case you'd use mutexes.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Sun Jun 7 14:44:34 2026
    On Sat, 6 Jun 2026 13:11:52 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/6/2026 3:45 AM, boltar@caprica.universe wrote:
    Provide some example code, I have better things to do than figure it out.


    Ask and you shall receive. Here is the actual header infrastructure I
    use in my own engine for tracking resources. It includes both the lean

    Are you seriously suggesting I should use that mess in my code in order
    NOT to use heavily tested, optimised and debugged standard library class??

    You're a comedian.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Sun Jun 7 20:02:59 2026
    On 07/06/2026 16:40, boltar@caprica.universe wrote:
    On Sat, 6 Jun 2026 20:59:03 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 05/06/2026 16:45, boltar@caprica.universe wrote:
    I'm talking about server in the software sense, not hardware.

    In the software sense, you can have "servers" on all sorts of things.˙ I

    I guess it depends how you define server. I define it as something that has other programs connecting to it via sockets, pipes, whatever. Its not a daemon that just sits there doing its own thing.


    Fair enough. I would agree that the term "server" - while not strictly defined - would imply listening for commands or requests from some other program, and acting upon them.

    Feel free to show how to link C pointers with atomics to do pointer
    reference counting because I have better things to do.


    Have you a rough idea how the shared pointers you are using now are
    implemented?˙ An implementation that does not use atomics, because
    atomic accesses are unnecessary overhead on a single-threaded system,
    are the same - except the reference counts are normal integer types,
    not atomic types.

    Atomics are great if all you need to do is a single uninterruptable operation.

    Yes. But the nice thing about single-threaded programming is that
    everything is already uninterruptable, as you have nothing else that can
    do any interrupting. (Well, you might use signals in your program.)

    The cost of atomics on a multi-core system is that there needs to be
    some kind of broadcast across cores and/or caches to make sure that
    nothing else on a different core interrupts the operation. Simple loads
    and stores are usually okay, but you have to be sure that during a read-modify-write operation, nothing else is jumping in and modifying
    the data at the address in the middle of the atomic operation.
    Solutions vary by architecture, with many architectures supporting
    several options, but they include system-wide bus locks, cache
    broadcasts, compare-and-swap operations in loops, and load/store with reservation. Some of these can be quite costly, though on x86 a simple fetch-and-add atomic access is not bad.

    Then there is the synchronisation. Processors, caches and bus
    controllers re-order all sorts of things in different ways. It is
    usually critical in the situations where atomics are used that other
    memory accesses are ordered, or at least observable. That can mean
    flushes of write buffers, discarding speculative execution or loads, and
    more.

    All this can add up to a large number of processor cycles - and all of
    it is unnecessary for single-threaded programs. But run-time efficiency
    is not always important - maybe this overhead is negligible in your use
    case, in which case the convenience of pre-written shared pointers could easily outweigh the efficiency costs. That is, of course, entirely up
    to you.

    They're useless if you need to do a whole load of things in
    sequence
    which I suspect shared ptr has to do and in which case you'd use mutexes.


    I don't think a shared pointer needs to do much more than some atomic counting, and won't need a mutex - at least, not for the most common
    cases. (And with a single-threaded program, there should never be any
    clashes or contesting of accesses.)


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Sun Jun 7 11:48:59 2026
    On 6/7/2026 7:44 AM, boltar@caprica.universe wrote:
    On Sat, 6 Jun 2026 13:11:52 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/6/2026 3:45 AM, boltar@caprica.universe wrote:
    Provide some example code, I have better things to do than figure it
    out.


    Ask and you shall receive. Here is the actual header infrastructure I
    use in my own engine for tracking resources. It includes both the lean

    Are you seriously suggesting I should use that mess in my code in order
    NOT to use heavily tested, optimised and debugged standard library class??

    You're a comedian.



    Well, up to you! I accidentally posted some older code of mine. I made a
    new post showing my iptr. Its crude, but it just happens to work for me
    in my single threaded engine.

    simple/crude intrusive ref count for boltar...
    6/6/2026, 9:02 PM

    So, well, print it out on toilet paper and use it when the time calls
    for it? Or use it in another way? Perhaps. Well, its all up to you
    anyway, right? ;^)

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Mon Jun 8 08:16:02 2026
    On Sun, 7 Jun 2026 20:02:59 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 07/06/2026 16:40, boltar@caprica.universe wrote:
    Atomics are great if all you need to do is a single uninterruptable
    operation.

    Yes. But the nice thing about single-threaded programming is that >everything is already uninterruptable, as you have nothing else that can
    do any interrupting. (Well, you might use signals in your program.)

    IIRC signals can be all be caught in a single thread using sigwait() or something like that with all the other threads ignoring them so shouldn't
    be an issue.

    The cost of atomics on a multi-core system is that there needs to be
    some kind of broadcast across cores and/or caches to make sure that
    nothing else on a different core interrupts the operation. Simple loads
    and stores are usually okay, but you have to be sure that during a >read-modify-write operation, nothing else is jumping in and modifying
    the data at the address in the middle of the atomic operation.
    Solutions vary by architecture, with many architectures supporting
    several options, but they include system-wide bus locks, cache
    broadcasts, compare-and-swap operations in loops, and load/store with >reservation. Some of these can be quite costly, though on x86 a simple >fetch-and-add atomic access is not bad.

    Then there is the synchronisation. Processors, caches and bus
    controllers re-order all sorts of things in different ways. It is
    usually critical in the situations where atomics are used that other
    memory accesses are ordered, or at least observable. That can mean
    flushes of write buffers, discarding speculative execution or loads, and >more.

    I'm sure all the above is correct but its way too low level for me to worry about. My code isn't sitting in a tight CPU loop, it spends most of its time doing nothing waiting for client input with a once a second interrupt timer
    to update the objects.

    I don't think a shared pointer needs to do much more than some atomic >counting, and won't need a mutex - at least, not for the most common

    shared_ptr<myclass> sp2 = sp1

    at the very least has to do 2 operations (after basic object memory has been allocated): Copy the raw pointer and update a reference count. You can't
    do that with an atomic operation.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Mon Jun 8 08:18:41 2026
    On Sun, 7 Jun 2026 11:48:59 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/7/2026 7:44 AM, boltar@caprica.universe wrote:
    On Sat, 6 Jun 2026 13:11:52 -0700
    "Chris M. Thomasson" <chris.m.thomasson.1@gmail.com> gabbled:
    On 6/6/2026 3:45 AM, boltar@caprica.universe wrote:
    Provide some example code, I have better things to do than figure it
    out.


    Ask and you shall receive. Here is the actual header infrastructure I
    use in my own engine for tracking resources. It includes both the lean

    Are you seriously suggesting I should use that mess in my code in order
    NOT to use heavily tested, optimised and debugged standard library class?? >>
    You're a comedian.



    Well, up to you! I accidentally posted some older code of mine. I made a
    new post showing my iptr. Its crude, but it just happens to work for me
    in my single threaded engine.

    Whether it works is not the issue, I'm sure it does. But I have an allergic reaction against re-inventing the wheel when its not necessary and said new wheel probably won't be as good as the current one. No doubt someone could write some whizzy new roll-their-own vector class thats 1% more efficient in some edge case but worse in all the others, so whats the point?


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Mon Jun 8 10:57:33 2026
    On 08/06/2026 10:16, boltar@caprica.universe wrote:
    On Sun, 7 Jun 2026 20:02:59 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 07/06/2026 16:40, boltar@caprica.universe wrote:
    Atomics are great if all you need to do is a single uninterruptable
    operation.

    Yes.˙ But the nice thing about single-threaded programming is that
    everything is already uninterruptable, as you have nothing else that
    can do any interrupting.˙ (Well, you might use signals in your program.)

    IIRC signals can be all be caught in a single thread using sigwait() or something like that with all the other threads ignoring them so shouldn't
    be an issue.

    The cost of atomics on a multi-core system is that there needs to be
    some kind of broadcast across cores and/or caches to make sure that
    nothing else on a different core interrupts the operation.˙ Simple
    loads and stores are usually okay, but you have to be sure that during
    a read-modify-write operation, nothing else is jumping in and
    modifying the data at the address in the middle of the atomic
    operation. Solutions vary by architecture, with many architectures
    supporting several options, but they include system-wide bus locks,
    cache broadcasts, compare-and-swap operations in loops, and load/store
    with reservation.˙ Some of these can be quite costly, though on x86 a
    simple fetch-and-add atomic access is not bad.

    Then there is the synchronisation.˙ Processors, caches and bus
    controllers re-order all sorts of things in different ways.˙ It is
    usually critical in the situations where atomics are used that other
    memory accesses are ordered, or at least observable.˙ That can mean
    flushes of write buffers, discarding speculative execution or loads,
    and more.

    I'm sure all the above is correct but its way too low level for me to worry about. My code isn't sitting in a tight CPU loop, it spends most of its
    time
    doing nothing waiting for client input with a once a second interrupt timer to update the objects.

    OK. That's often the case - most code is not time-critical, and then
    you focus on other things (like developer efficiency). Most of my PC programming is in Python - it doesn't matter if the code is fast or slow
    when you are waiting on network packets anyway.


    I don't think a shared pointer needs to do much more than some atomic
    counting, and won't need a mutex - at least, not for the most common

    shared_ptr<myclass> sp2 = sp1

    at the very least has to do 2 operations (after basic object memory has
    been
    allocated): Copy the raw pointer and update a reference count. You can't
    do that with an atomic operation.


    Only the count update has to be atomic. Remember, a "shared_ptr"
    instance does not hold any of the important information, such as the
    counters - it only holds a copy of a pointer to the original object, and
    a pointer to the control block. And because you are coping an existing
    shared pointer, the control block already exists and its counters are definitely non-zero - these pointers can be happily copied without any
    kind of locking. The only special thing is that the atomic counter in
    the control block must be incremented atomically, in case (in another
    thread) another shared pointer to the same object is being created or destroyed.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Mon Jun 8 09:39:23 2026
    On Mon, 8 Jun 2026 10:57:33 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 08/06/2026 10:16, boltar@caprica.universe wrote:
    at the very least has to do 2 operations (after basic object memory has
    been
    allocated): Copy the raw pointer and update a reference count. You can't
    do that with an atomic operation.


    Only the count update has to be atomic. Remember, a "shared_ptr"
    instance does not hold any of the important information, such as the >counters - it only holds a copy of a pointer to the original object, and
    a pointer to the control block. And because you are coping an existing >shared pointer, the control block already exists and its counters are >definitely non-zero - these pointers can be happily copied without any
    kind of locking. The only special thing is that the atomic counter in
    the control block must be incremented atomically, in case (in another >thread) another shared pointer to the same object is being created or >destroyed.

    And thats the problem:

    Thread 2:
    shared_ptr<myclass> sp2 = sp1;
    Allocate memory for sp2
    Thread 1 interrupts:
    sp1 is deleted
    decrements reference counter which goes to 0 so deletes control block Thread 2 resumes:
    Oops, control block has vanished!


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Mon Jun 8 12:26:28 2026
    On 08/06/2026 11:39, boltar@caprica.universe wrote:
    On Mon, 8 Jun 2026 10:57:33 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 08/06/2026 10:16, boltar@caprica.universe wrote:
    at the very least has to do 2 operations (after basic object memory
    has been
    allocated): Copy the raw pointer and update a reference count. You can't >>> do that with an atomic operation.


    Only the count update has to be atomic.˙ Remember, a "shared_ptr"
    instance does not hold any of the important information, such as the
    counters - it only holds a copy of a pointer to the original object,
    and a pointer to the control block.˙ And because you are coping an
    existing shared pointer, the control block already exists and its
    counters are definitely non-zero - these pointers can be happily
    copied without any kind of locking.˙ The only special thing is that
    the atomic counter in the control block must be incremented
    atomically, in case (in another thread) another shared pointer to the
    same object is being created or destroyed.

    And thats the problem:

    Thread 2:
    ˙˙˙˙shared_ptr<myclass> sp2 = sp1;
    ˙˙˙˙Allocate memory for sp2
    Thread 1 interrupts:
    ˙˙˙˙sp1 is deleted
    ˙˙˙˙decrements reference counter which goes to 0 so deletes control block Thread 2 resumes:
    ˙˙˙˙Oops, control block has vanished!


    If sp1 is deleted by one thread while another thread is reading from it
    (to initialise sp2), that's a race condition and UB. It's the same as
    any other uncontrolled mix of reads and writes from different threads.

    But to make it possible to make such mistakes, you'd probably have to be
    doing something weird such as using raw pointers to your shared_ptr<> instances instead of using shared_ptr<> objects directly. At the very
    least, you'd have to be sharing the same shared_ptr<> instance from
    different threads without any control - which is exactly what you don't
    do with shared_ptr<>'s.

    share_ptr<> makes it very difficult to get things wrong, but it can't
    stop all attempts at creative abuse.



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Mon Jun 8 10:50:39 2026
    On Mon, 8 Jun 2026 12:26:28 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 08/06/2026 11:39, boltar@caprica.universe wrote:
    Thread 2:
    ˙˙˙˙shared_ptr<myclass> sp2 = sp1;
    ˙˙˙˙Allocate memory for sp2
    Thread 1 interrupts:
    ˙˙˙˙sp1 is deleted
    ˙˙˙˙decrements reference counter which goes to 0 so deletes control
    block
    Thread 2 resumes:
    ˙˙˙˙Oops, control block has vanished!


    If sp1 is deleted by one thread while another thread is reading from it
    (to initialise sp2), that's a race condition and UB. It's the same as
    any other uncontrolled mix of reads and writes from different threads.

    But to make it possible to make such mistakes, you'd probably have to be >doing something weird such as using raw pointers to your shared_ptr<> >instances instead of using shared_ptr<> objects directly. At the very >least, you'd have to be sharing the same shared_ptr<> instance from >different threads without any control - which is exactly what you don't
    do with shared_ptr<>'s.

    share_ptr<> makes it very difficult to get things wrong, but it can't
    stop all attempts at creative abuse.

    shared_ptr is thread safe.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Mon Jun 8 13:20:33 2026
    On 08/06/2026 12:50, boltar@caprica.universe wrote:
    On Mon, 8 Jun 2026 12:26:28 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 08/06/2026 11:39, boltar@caprica.universe wrote:
    Thread 2:
    ˙˙˙˙˙shared_ptr<myclass> sp2 = sp1;
    ˙˙˙˙˙Allocate memory for sp2
    Thread 1 interrupts:
    ˙˙˙˙˙sp1 is deleted
    ˙˙˙˙˙decrements reference counter which goes to 0 so deletes control
    block
    Thread 2 resumes:
    ˙˙˙˙˙Oops, control block has vanished!


    If sp1 is deleted by one thread while another thread is reading from
    it (to initialise sp2), that's a race condition and UB.˙ It's the same
    as any other uncontrolled mix of reads and writes from different threads.

    But to make it possible to make such mistakes, you'd probably have to
    be doing something weird such as using raw pointers to your
    shared_ptr<> instances instead of using shared_ptr<> objects
    directly.˙ At the very least, you'd have to be sharing the same
    shared_ptr<> instance from different threads without any control -
    which is exactly what you don't do with shared_ptr<>'s.

    share_ptr<> makes it very difficult to get things wrong, but it can't
    stop all attempts at creative abuse.

    shared_ptr is thread safe.


    When used correctly, yes. Trying to delete sp1 from one thread while
    trying to use sp1 as the source for an assignment constructor in another thread is incorrect use, and will not be safe.

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From boltar@3:633/10 to All on Mon Jun 8 14:46:48 2026
    On Mon, 8 Jun 2026 13:20:33 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 08/06/2026 12:50, boltar@caprica.universe wrote:
    On Mon, 8 Jun 2026 12:26:28 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 08/06/2026 11:39, boltar@caprica.universe wrote:
    Thread 2:
    ˙˙˙˙˙shared_ptr<myclass> sp2 = sp1;
    ˙˙˙˙˙Allocate memory for sp2
    Thread 1 interrupts:
    ˙˙˙˙˙sp1 is deleted
    ˙˙˙˙˙decrements reference counter which goes to 0 so deletes control
    block
    Thread 2 resumes:
    ˙˙˙˙˙Oops, control block has vanished!


    If sp1 is deleted by one thread while another thread is reading from
    it (to initialise sp2), that's a race condition and UB.˙ It's the same
    as any other uncontrolled mix of reads and writes from different threads. >>>
    But to make it possible to make such mistakes, you'd probably have to
    be doing something weird such as using raw pointers to your
    shared_ptr<> instances instead of using shared_ptr<> objects
    directly.˙ At the very least, you'd have to be sharing the same
    shared_ptr<> instance from different threads without any control -
    which is exactly what you don't do with shared_ptr<>'s.

    share_ptr<> makes it very difficult to get things wrong, but it can't
    stop all attempts at creative abuse.

    shared_ptr is thread safe.


    When used correctly, yes. Trying to delete sp1 from one thread while
    trying to use sp1 as the source for an assignment constructor in another >thread is incorrect use, and will not be safe.

    Seems a bit of an oversight. Either a class is threadsafe or it isn't,
    there's no point doing only half of it because what happens if someone forgets? Might as well not bother.


    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From David Brown@3:633/10 to All on Mon Jun 8 17:19:09 2026
    On 08/06/2026 16:46, boltar@caprica.universe wrote:
    On Mon, 8 Jun 2026 13:20:33 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 08/06/2026 12:50, boltar@caprica.universe wrote:
    On Mon, 8 Jun 2026 12:26:28 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 08/06/2026 11:39, boltar@caprica.universe wrote:
    Thread 2:
    ˙˙˙˙˙shared_ptr<myclass> sp2 = sp1;
    ˙˙˙˙˙Allocate memory for sp2
    Thread 1 interrupts:
    ˙˙˙˙˙sp1 is deleted
    ˙˙˙˙˙decrements reference counter which goes to 0 so deletes control
    block
    Thread 2 resumes:
    ˙˙˙˙˙Oops, control block has vanished!


    If sp1 is deleted by one thread while another thread is reading from
    it (to initialise sp2), that's a race condition and UB.˙ It's the
    same as any other uncontrolled mix of reads and writes from
    different threads.

    But to make it possible to make such mistakes, you'd probably have
    to be doing something weird such as using raw pointers to your
    shared_ptr<> instances instead of using shared_ptr<> objects
    directly.˙ At the very least, you'd have to be sharing the same
    shared_ptr<> instance from different threads without any control -
    which is exactly what you don't do with shared_ptr<>'s.

    share_ptr<> makes it very difficult to get things wrong, but it
    can't stop all attempts at creative abuse.

    shared_ptr is thread safe.


    When used correctly, yes.˙ Trying to delete sp1 from one thread while
    trying to use sp1 as the source for an assignment constructor in
    another thread is incorrect use, and will not be safe.

    Seems a bit of an oversight. Either a class is threadsafe or it isn't, there's no point doing only half of it because what happens if someone forgets?
    Might as well not bother.


    No, not at all. The class is thread-safe - it is not idiot-proof. The
    same applies to everything else in the C++ standard library. For all
    sorts of functions and classes, there are requirements about how you use
    them - if you ignore those requirements, you get UB. As I said, you
    have to try hard to make a mess of shared_ptr<> code, but it is
    possible. The situation you describe is clearly that kind of mess.

    There are basically four ways to have thread-safe code (in any language,
    not just C++) :

    1. Use atomic accesses. This is suitable for small accesses and operations.

    2. Use locks consistently before accessing shared data.

    3. Use library or language facilities that do 1 or 2 for you.

    4. Write your code so that there is no way to get a race condition on
    the given object - so no concurrent writes with reads or other writes.
    An excellent way to do that is to avoid shared objects.


    shared_ptr<> lets you have thread-safe access to a shared control block
    and to handle destruction of its shared managed object and associated
    storage. It does not let you have thread-safe sharing of shared_ptr<> objects, because that would be silly - a shared_ptr<> instance is just
    two pointers in size, and will (in sane code) usually be very local. It
    is thread-safe by method 4 above.

    shared_ptr<> in itself does not in itself make the managed object
    thread-safe. It is up to the programmer to do that, in whatever way is appropriate.


    <https://cppreference.com/cpp/memory/shared_ptr>



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Jun 8 11:59:37 2026
    On 6/6/2026 10:28 PM, Bonita Montero wrote:
    Am 06.06.2026 um 22:07 schrieb Chris M. Thomasson:

    You are missing the point...? Why use it on a single threaded system
    anyway? What is a LOCK XADD vs a RMW without any LOCK, or just in code
    wrt a non-atomic RMW (++count)? If there is no second thread, there is
    no one to synchronize with. Paying a synchronization tax when there is
    no concurrency is just bad engineering?
    If the price is small it's not bad engineering.

    Paying that price is a load server loop? It adds up, right?

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Chris M. Thomasson@3:633/10 to All on Mon Jun 8 12:10:47 2026
    On 6/8/2026 3:50 AM, boltar@caprica.universe wrote:
    On Mon, 8 Jun 2026 12:26:28 +0200
    David Brown <david.brown@hesbynett.no> gabbled:
    On 08/06/2026 11:39, boltar@caprica.universe wrote:
    Thread 2:
    ˙˙˙˙˙shared_ptr<myclass> sp2 = sp1;
    ˙˙˙˙˙Allocate memory for sp2
    Thread 1 interrupts:
    ˙˙˙˙˙sp1 is deleted
    ˙˙˙˙˙decrements reference counter which goes to 0 so deletes control
    block
    Thread 2 resumes:
    ˙˙˙˙˙Oops, control block has vanished!


    If sp1 is deleted by one thread while another thread is reading from
    it (to initialise sp2), that's a race condition and UB.˙ It's the same
    as any other uncontrolled mix of reads and writes from different threads.

    But to make it possible to make such mistakes, you'd probably have to
    be doing something weird such as using raw pointers to your
    shared_ptr<> instances instead of using shared_ptr<> objects
    directly.˙ At the very least, you'd have to be sharing the same
    shared_ptr<> instance from different threads without any control -
    which is exactly what you don't do with shared_ptr<>'s.

    share_ptr<> makes it very difficult to get things wrong, but it can't
    stop all attempts at creative abuse.

    shared_ptr is thread safe.


    afaict, shared_ptr only provides _basic_ thread safety wrt ref counting.
    Its not a _strong_ atomic ref counter! Meaning that a thread that does
    not already own a reference cannot take a reference. I think C++ allows
    for std::atomic<std::shared_ptr<T>>? That gives you strong refcounting
    using whatever means necessary, crap impl or not. Fwiw, here is an
    example of a strong reference counter:

    https://patents.google.com/patent/US20060037026A1/en?oq=20060037026

    Joe Seigh noticed that its basically a total rip off of his atomic_ptr
    from back in the comp.programming.threads days, and even before that.

    Read my post here:

    "Some history wrt reference counting..."



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Wed Jun 10 15:28:55 2026
    Am 08.06.2026 um 17:19 schrieb David Brown:

    shared_ptr<> lets you have thread-safe access to a shared control block
    and to handle destruction of its shared managed object and associated storage.˙ It does not let you have thread-safe sharing of shared_ptr<> objects, because that would be silly - a shared_ptr<> instance is just
    two pointers in size, and will (in sane code) usually be very local.˙ It
    is thread-safe by method 4 above.

    Yes, you need an atomic<shared_ptr<T>> for that !!!1111



    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Wed Jun 10 15:42:59 2026
    Am 08.06.2026 um 21:10 schrieb Chris M. Thomasson:

    afaict, shared_ptr only provides _basic_ thread safety wrt ref counting.
    Its not a _strong_ atomic ref counter! Meaning that a thread that does
    not already own a reference cannot take a reference. I think C++ allows
    for std::atomic<std::shared_ptr<T>>? That gives you strong refcounting
    using whatever means necessary, crap impl or not. Fwiw, here is an
    example of a strong reference counter:

    There's one point missig of shared_ptr<> in conjunction with atomic<shared_ptr<>>:
    shared_ptr<T> has an assignment operator that takes the same type on the
    right side. So if you assign an atomic<shared_ptr<>> to a shared_ptr<> the first is converted to a shared_ptr<> temporary which is then assigned to the shared_ptr<>. This makes a lot of cacheline flipping if you do that often. shared_ptr<> should have an assignment operator that directly takes a
    reference to an atomic<shared_ptr<>>. This operator should first compare
    it's internal pointer value against the atomic<shared_ptr<>>. Doing that
    would remain the cacheline holding the atomic<shared_ptr<>> in shared
    mode so that the assignment takes about a nanosecond until it quits
    because of identical values.
    This would make it possible to store a persitent / thread_local
    shared_ptr<> referring to the shared data structure which is periodi-
    cally polled against the atomic<shared_ptr<>> very quickly. This would
    could keep the latest version it has seen and the update mostly costs
    nearly nothing.
    I implemented that in two classes calles shared_obj<> and tshared_obj<>;
    the latter one is the atomic variant. I benchmarked the cost of an up-
    date and it was in the mentioned range of a nanosecond (clang++).


    https://patents.google.com/patent/US20060037026A1/en?oq=20060037026

    Joe Seigh noticed that its basically a total rip off of his atomic_ptr
    from back in the comp.programming.threads days, and even before that.

    Read my post here:

    "Some history wrt reference counting..."




    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Bonita Montero@3:633/10 to All on Wed Jun 10 15:44:20 2026
    Am 08.06.2026 um 16:46 schrieb boltar@caprica.universe:

    Seems a bit of an oversight. Either a class is threadsafe or it isn't, there's no point doing only half of it because what happens if someone forgets?

    I've written classes whose methods are partitially thread-safe
    and others need locking of an outer context so that the locking
    can survive several function calls to reduce the locking-overhead.

    This is such a class:

    #pragma once
    #include <vector>
    #include <cstdint>
    #include <atomic>
    #include <algorithm>
    #include <span>
    #include <ranges>
    #include <utility>
    #include <concepts>
    #include <cassert>
    #if defined(_MSC_VER)
    #include <intrin.h>
    #elif defined(__GNUC__) || defined(__clang__)
    #include <x86intrin.h>
    #endif
    #include "ndi.hpp"
    #include "cacheline.hpp"
    #undef min

    #if defined(__clang__)
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wunqualified-std-cast-call"
    #pragma clang diagnostic ignored "-Wlogical-op-parentheses"
    #endif

    template<typename T>
    concept atomic_log_type = std::is_nothrow_destructible_v<T>;

    template<atomic_log_type T>
    struct atomic_log
    {
    atomic_log( size_t capacity );
    ~atomic_log();
    template<typename ... Args>
    bool append( Args &&... args )
    requires std::is_nothrow_constructible_v<T, Args ...>;
    template<std::random_access_iterator Iterator>
    Iterator append( Iterator begin, Iterator end )
    requires std::is_nothrow_constructible_v<T, std::iter_value_t<Iterator> &&>;
    std::span<T> range() noexcept;
    std::span<const T> range() const noexcept;
    void clear() noexcept;
    bool resize( size_t capacity ) noexcept
    requires std::is_nothrow_move_constructible_v<T>;
    size_t size() const noexcept;
    private:
    static constexpr bool Index64 = std::atomic<uint64_t>::is_always_lock_free;
    using index = std::conditional_t<Index64, uint64_t, size_t>;
    static_assert( std::atomic<index>::is_always_lock_free );
    std::vector<ndi<T>> m_log;
    alignas( CL_SIZE ) index m_end;
    std::pair<size_t, size_t> add( size_t n ) noexcept;
    };

    template<atomic_log_type T>
    inline atomic_log<T>::atomic_log( size_t size ) :
    m_log( size ),
    m_end( 0 )
    {
    }

    template<atomic_log_type T>
    inline atomic_log<T>::~atomic_log()
    {
    clear();
    }

    template<atomic_log_type T>
    template<typename ... Args >
    bool atomic_log<T>::append( Args &&... args )
    requires std::is_nothrow_constructible_v<T, Args ...>
    {
    using namespace std;
    pair<size_t, size_t> nWhere = add( 1 );
    if( !nWhere.first ) [[unlikely]]
    return false;
    m_log[nWhere.second].emplace( forward<Args>( args ) ... );
    return true;
    }

    template<atomic_log_type T>
    template<std::random_access_iterator Iterator>
    Iterator atomic_log<T>::append( Iterator begin, Iterator end )
    requires std::is_nothrow_constructible_v<T, std::iter_value_t<Iterator> &&>
    {
    using namespace std;
    if( end <= begin )
    return end;
    size_t n = end - begin;
    pair<size_t, size_t> nWhere = add( n );
    if( !nWhere.first ) [[unlikely]]
    return end;
    auto
    dst = m_log.begin() + nWhere.second,
    dstEnd = dst + nWhere.first;
    Iterator src = begin;
    do
    dst++->emplace( move( *src++ ) );
    while( dst < dstEnd );
    return move( src, end, begin );
    }

    template<atomic_log_type T>
    std::span<T> atomic_log<T>::range() noexcept
    {
    size_t size = this->size();
    if( !size ) [[unlikely]]
    return std::span<T>();
    return std::span<T>( &*m_log.begin(), size );
    }

    template<atomic_log_type T>
    std::span<const T> atomic_log<T>::range() const noexcept
    {
    size_t size = this->size();
    if( !size ) [[unlikely]]
    return std::span<const T>();
    return std::span<const T>( &*m_log.begin(), size );
    }

    template<atomic_log_type T>
    inline void atomic_log<T>::clear() noexcept
    {
    using namespace std;
    span<ndi<T>> range( m_log.begin(), m_log.begin() + size() );
    for( ndi<T> &value : views::reverse( range ) )
    value.destruct();
    m_end = 0;
    }

    template<atomic_log_type T>
    bool atomic_log<T>::resize( size_t capacity ) noexcept
    requires std::is_nothrow_move_constructible_v<T>
    {
    using namespace std;
    size_t size = this->size();
    if( size >= capacity )
    return size == capacity;
    vector<ndi<T>> newLog( capacity );
    for( size_t i = 0; i < size; ++i )
    {
    newLog[i].emplace( move( m_log[i] ) );
    m_log[i].destruct();
    }
    m_log = move( newLog );
    return true;
    }

    template<atomic_log_type T>
    inline size_t atomic_log<T>::size() const noexcept
    {
    using namespace std;
    return (size_t)min( m_end, (index)m_log.size() );
    }

    template<atomic_log_type T>
    inline auto atomic_log<T>::add( size_t n ) noexcept -> std::pair<size_t, size_t>
    {
    using namespace std;
    atomic_ref aEnd( m_end );
    if constexpr( Index64 )
    {
    uint64_t where = aEnd.fetch_add( n, memory_order_relaxed );
    if( where >= m_log.size() )
    return pair<size_t, size_t>( 0, 0 );
    n = min( m_log.size() - (size_t)where, n );
    return pair<size_t, size_t>( n, (size_t)where );
    }
    else
    {
    size_t where = aEnd.load( memory_order_relaxed ), niu;
    assert(where <= m_log.size());
    do
    if( where == m_log.size() ) [[unlikely]]
    return pair<size_t, size_t>( 0, 0 );
    else if( (uint64_t)where + n <= m_log.size() ) [[likely]]
    niu = where + n;
    else
    niu = m_log.size();
    while( !aEnd.compare_exchange_strong( where, niu, memory_order_relaxed, memory_order_relaxed ) );
    n = min( m_log.size() - where, n );
    return pair<size_t, size_t>( n, where );
    }
    }

    #if defined(__clang__)
    #pragma clang diagnostic pop
    #endif

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)