Regarding shared_ptr reference count block












2















I had 2 questions regarding the std::shared_ptr control block:



(1) Regarding size:
How can I programatically find the exact size of the control block for a std::shared_ptr?



(2) Regarding logic:
Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?










share|improve this question

























  • I'd be happy to revise the question if there's something wrong or unclear about it?(to the person who downvoted it)

    – tangy
    Nov 26 '18 at 4:03








  • 4





    std::shared_ptr is a template, and, as such, since templates must be visible to the compiler, there's nothing that stops you from investigating your compiler's implementation of std::shared_ptr, and figure this out.

    – Sam Varshavchik
    Nov 26 '18 at 4:07











  • True, but I'm hoping to do so programatically if possible.

    – tangy
    Nov 26 '18 at 4:08






  • 2





    You can look at the implementation of std::shared_ptr of your compiler. Other than that, I think std::shared_ptr just uses atomics so is "lock free" in that regard. Note that ideally, you don't share mutable date across threads at all if you can help it and in that case you'll probably want simpler ref counted pointer that doesn't require atomics or locks.

    – Cubic
    Nov 26 '18 at 4:09






  • 2





    @tangy There is no guarantee that std::shared_ptr is implemented in term of standard atomic types either. Either way, why do you care?

    – curiousguy
    Nov 26 '18 at 18:11
















2















I had 2 questions regarding the std::shared_ptr control block:



(1) Regarding size:
How can I programatically find the exact size of the control block for a std::shared_ptr?



(2) Regarding logic:
Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?










share|improve this question

























  • I'd be happy to revise the question if there's something wrong or unclear about it?(to the person who downvoted it)

    – tangy
    Nov 26 '18 at 4:03








  • 4





    std::shared_ptr is a template, and, as such, since templates must be visible to the compiler, there's nothing that stops you from investigating your compiler's implementation of std::shared_ptr, and figure this out.

    – Sam Varshavchik
    Nov 26 '18 at 4:07











  • True, but I'm hoping to do so programatically if possible.

    – tangy
    Nov 26 '18 at 4:08






  • 2





    You can look at the implementation of std::shared_ptr of your compiler. Other than that, I think std::shared_ptr just uses atomics so is "lock free" in that regard. Note that ideally, you don't share mutable date across threads at all if you can help it and in that case you'll probably want simpler ref counted pointer that doesn't require atomics or locks.

    – Cubic
    Nov 26 '18 at 4:09






  • 2





    @tangy There is no guarantee that std::shared_ptr is implemented in term of standard atomic types either. Either way, why do you care?

    – curiousguy
    Nov 26 '18 at 18:11














2












2








2








I had 2 questions regarding the std::shared_ptr control block:



(1) Regarding size:
How can I programatically find the exact size of the control block for a std::shared_ptr?



(2) Regarding logic:
Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?










share|improve this question
















I had 2 questions regarding the std::shared_ptr control block:



(1) Regarding size:
How can I programatically find the exact size of the control block for a std::shared_ptr?



(2) Regarding logic:
Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?







c++ multithreading c++11 shared-ptr lock-free






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 26 '18 at 13:37









curiousguy

4,65223046




4,65223046










asked Nov 26 '18 at 3:45









tangytangy

1,029721




1,029721













  • I'd be happy to revise the question if there's something wrong or unclear about it?(to the person who downvoted it)

    – tangy
    Nov 26 '18 at 4:03








  • 4





    std::shared_ptr is a template, and, as such, since templates must be visible to the compiler, there's nothing that stops you from investigating your compiler's implementation of std::shared_ptr, and figure this out.

    – Sam Varshavchik
    Nov 26 '18 at 4:07











  • True, but I'm hoping to do so programatically if possible.

    – tangy
    Nov 26 '18 at 4:08






  • 2





    You can look at the implementation of std::shared_ptr of your compiler. Other than that, I think std::shared_ptr just uses atomics so is "lock free" in that regard. Note that ideally, you don't share mutable date across threads at all if you can help it and in that case you'll probably want simpler ref counted pointer that doesn't require atomics or locks.

    – Cubic
    Nov 26 '18 at 4:09






  • 2





    @tangy There is no guarantee that std::shared_ptr is implemented in term of standard atomic types either. Either way, why do you care?

    – curiousguy
    Nov 26 '18 at 18:11



















  • I'd be happy to revise the question if there's something wrong or unclear about it?(to the person who downvoted it)

    – tangy
    Nov 26 '18 at 4:03








  • 4





    std::shared_ptr is a template, and, as such, since templates must be visible to the compiler, there's nothing that stops you from investigating your compiler's implementation of std::shared_ptr, and figure this out.

    – Sam Varshavchik
    Nov 26 '18 at 4:07











  • True, but I'm hoping to do so programatically if possible.

    – tangy
    Nov 26 '18 at 4:08






  • 2





    You can look at the implementation of std::shared_ptr of your compiler. Other than that, I think std::shared_ptr just uses atomics so is "lock free" in that regard. Note that ideally, you don't share mutable date across threads at all if you can help it and in that case you'll probably want simpler ref counted pointer that doesn't require atomics or locks.

    – Cubic
    Nov 26 '18 at 4:09






  • 2





    @tangy There is no guarantee that std::shared_ptr is implemented in term of standard atomic types either. Either way, why do you care?

    – curiousguy
    Nov 26 '18 at 18:11

















I'd be happy to revise the question if there's something wrong or unclear about it?(to the person who downvoted it)

– tangy
Nov 26 '18 at 4:03







I'd be happy to revise the question if there's something wrong or unclear about it?(to the person who downvoted it)

– tangy
Nov 26 '18 at 4:03






4




4





std::shared_ptr is a template, and, as such, since templates must be visible to the compiler, there's nothing that stops you from investigating your compiler's implementation of std::shared_ptr, and figure this out.

– Sam Varshavchik
Nov 26 '18 at 4:07





std::shared_ptr is a template, and, as such, since templates must be visible to the compiler, there's nothing that stops you from investigating your compiler's implementation of std::shared_ptr, and figure this out.

– Sam Varshavchik
Nov 26 '18 at 4:07













True, but I'm hoping to do so programatically if possible.

– tangy
Nov 26 '18 at 4:08





True, but I'm hoping to do so programatically if possible.

– tangy
Nov 26 '18 at 4:08




2




2





You can look at the implementation of std::shared_ptr of your compiler. Other than that, I think std::shared_ptr just uses atomics so is "lock free" in that regard. Note that ideally, you don't share mutable date across threads at all if you can help it and in that case you'll probably want simpler ref counted pointer that doesn't require atomics or locks.

– Cubic
Nov 26 '18 at 4:09





You can look at the implementation of std::shared_ptr of your compiler. Other than that, I think std::shared_ptr just uses atomics so is "lock free" in that regard. Note that ideally, you don't share mutable date across threads at all if you can help it and in that case you'll probably want simpler ref counted pointer that doesn't require atomics or locks.

– Cubic
Nov 26 '18 at 4:09




2




2





@tangy There is no guarantee that std::shared_ptr is implemented in term of standard atomic types either. Either way, why do you care?

– curiousguy
Nov 26 '18 at 18:11





@tangy There is no guarantee that std::shared_ptr is implemented in term of standard atomic types either. Either way, why do you care?

– curiousguy
Nov 26 '18 at 18:11












3 Answers
3






active

oldest

votes


















5















(1) Regarding size: How can I programatically find the exact size of the control block for a std::shared_ptr?




There is no way. It's not directly accessible.




(2) Regarding logic: Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?




Absolutely not. Lock-free implementations are not always better than implementations that use locks. Having an additional constraint, at best, doesn't make the implementation worse but it cannot possibly make the implementation better.



Consider two equally competent programmers each doing their best to implement shared_ptr. One must produce a lock-free implementation. The other is completely free to use their best judgment. There is simply no way the one that must produce a lock-free implementation can produce a better implementation all other things being equal. At best, a lock-free implementation is best and they'll both produce one. At worse, on this platform a lock-free implementation has huge disadvantages and one implementer must use one. Yuck.






share|improve this answer
























  • Do note that the Boost quote is not "completely lock-free"; it's merely lock-free "on most common platforms".

    – Nicol Bolas
    Nov 26 '18 at 4:30






  • 2





    @NicolBolas Yeah. It's probably just a statement about what the implementers judged to be best. It's hard to imagine how you would ever need or want a lock on any modern platform -- there's no case where any thread would ever need to wait for any other thread.

    – David Schwartz
    Nov 26 '18 at 4:34






  • 3





    C++11 only requires lock-free std::atomic_flag, which is enough to build a lock from, but not enough for lockless ref-counting. Putting a lock-free std::shared_ptr requirement / guarantee in the standard would theoretically restrict which platforms could support a conforming C++11 implementation. I think that's the reason, not that locking might be actually better on a normal platform where lock-free was possible.

    – Peter Cordes
    Nov 26 '18 at 20:57



















5














The control block is not exposed. In implementations I have read it is dynamic in size to store the deleter contiguously (and/or, in the case of make shared, the object itself).



In general it contains at least 3 pointer-size fields - weak, strong count, and deleter invoker.



At least one implementation relies on RTTI; others do not.



Operations on the count use atomic operations in the implementations I have read; note that C++ does not require atomic operatins to all be lock free (I believe a platform that doesn't have pointer-size lock-free operations can be a conforming C++ platform).



Their state is are consistent with each other and themselves, but no attempt to make them consistent with object state occurs. This is why using raw shared ptrs as copy on write pImpls may be error prone on some platforms.






share|improve this answer



















  • 1





    Yup, only std::atomic_flag is guaranteed lock-free. Good implementations on targets that can do it will use lock-free atomics for shared_ptr and atomic<T> up to a width the HW can support, of course.

    – Peter Cordes
    Nov 26 '18 at 21:00





















1














(1)
Of course it is best to check implementation, however you still may make some checks from your program.



Control block is allocated dynamically, so to determine its size you may overload new operator.



Then what you may also check is if std::make_shared provides you with some optimization of control block size.
In proper implementation I would expect that this will make two allocations (objectA and control block):



std::shared_ptr<A> i(new A());


However this will make only one allocation (and then objectA initialized with placement new):



auto a = std::make_shared<A>();


Consider following example:



#include <iostream>
#include <memory>

void * operator new(size_t size)
{
std::cout << "Requested allocation: " << size << std::endl;
void * p = malloc(size);
return p;
}

class A {};

class B
{
int a[8];
};

int main()
{
std::cout << "Sizeof int: " << sizeof(int) << ", A(empty): " << sizeof(A) << ", B(8 ints): " << sizeof(B) << std::endl;
{
std::cout << "Just new:" << std::endl;
std::cout << "- int:" << std::endl;
std::shared_ptr<int> i(new int());
std::cout << "- A(empty):" << std::endl;
std::shared_ptr<A> a(new A());
std::cout << "- B(8 ints):" << std::endl;
std::shared_ptr<B> b(new B());
}
{
std::cout << "Make shared:" << std::endl;
std::cout << "- int:" << std::endl;
auto i = std::make_shared<int>();
std::cout << "- A(empty):" << std::endl;
auto a = std::make_shared<A>();
std::cout << "- B(8 ints):" << std::endl;
auto b = std::make_shared<B>();
}
}


The output I received (of course it is hw architecture and compiler specific):



Sizeof int: 4, A(empty): 1, B(8 ints): 32
Just new:
- int:
Requested allocation: 4
Requested allocation: 24


First allocation for int - 4 bytes, next one for control block - 24 bytes.



- A(empty):
Requested allocation: 1
Requested allocation: 24
- B(8 ints):
Requested allocation: 32
Requested allocation: 24


Looks that control block is (most probably) 24 bytes.



Here is why to use make_shared:



Make shared:
- int:
Requested allocation: 24


Only one allocation, int + control block = 24 bytes, less then before.



- A(empty):
Requested allocation: 24
- B(8 ints):
Requested allocation: 48


Here one could expect 56 (32+24), but it looks that implementation is optimized. If you use make_shared - pointer to actual object is not needed in control block and its size is only 16 bytes.



Other possibility to check the size of control block is to:



std::cout<< sizeof(std::enable_shared_from_this<int>);


In my case:



16


So I would say that the size of control block in my case is 16-24 bytes, depending on how it was created.






share|improve this answer























    Your Answer






    StackExchange.ifUsing("editor", function () {
    StackExchange.using("externalEditor", function () {
    StackExchange.using("snippets", function () {
    StackExchange.snippets.init();
    });
    });
    }, "code-snippets");

    StackExchange.ready(function() {
    var channelOptions = {
    tags: "".split(" "),
    id: "1"
    };
    initTagRenderer("".split(" "), "".split(" "), channelOptions);

    StackExchange.using("externalEditor", function() {
    // Have to fire editor after snippets, if snippets enabled
    if (StackExchange.settings.snippets.snippetsEnabled) {
    StackExchange.using("snippets", function() {
    createEditor();
    });
    }
    else {
    createEditor();
    }
    });

    function createEditor() {
    StackExchange.prepareEditor({
    heartbeatType: 'answer',
    autoActivateHeartbeat: false,
    convertImagesToLinks: true,
    noModals: true,
    showLowRepImageUploadWarning: true,
    reputationToPostImages: 10,
    bindNavPrevention: true,
    postfix: "",
    imageUploader: {
    brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
    contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
    allowUrls: true
    },
    onDemand: true,
    discardSelector: ".discard-answer"
    ,immediatelyShowMarkdownHelp:true
    });


    }
    });














    draft saved

    draft discarded


















    StackExchange.ready(
    function () {
    StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53474539%2fregarding-shared-ptr-reference-count-block%23new-answer', 'question_page');
    }
    );

    Post as a guest















    Required, but never shown

























    3 Answers
    3






    active

    oldest

    votes








    3 Answers
    3






    active

    oldest

    votes









    active

    oldest

    votes






    active

    oldest

    votes









    5















    (1) Regarding size: How can I programatically find the exact size of the control block for a std::shared_ptr?




    There is no way. It's not directly accessible.




    (2) Regarding logic: Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?




    Absolutely not. Lock-free implementations are not always better than implementations that use locks. Having an additional constraint, at best, doesn't make the implementation worse but it cannot possibly make the implementation better.



    Consider two equally competent programmers each doing their best to implement shared_ptr. One must produce a lock-free implementation. The other is completely free to use their best judgment. There is simply no way the one that must produce a lock-free implementation can produce a better implementation all other things being equal. At best, a lock-free implementation is best and they'll both produce one. At worse, on this platform a lock-free implementation has huge disadvantages and one implementer must use one. Yuck.






    share|improve this answer
























    • Do note that the Boost quote is not "completely lock-free"; it's merely lock-free "on most common platforms".

      – Nicol Bolas
      Nov 26 '18 at 4:30






    • 2





      @NicolBolas Yeah. It's probably just a statement about what the implementers judged to be best. It's hard to imagine how you would ever need or want a lock on any modern platform -- there's no case where any thread would ever need to wait for any other thread.

      – David Schwartz
      Nov 26 '18 at 4:34






    • 3





      C++11 only requires lock-free std::atomic_flag, which is enough to build a lock from, but not enough for lockless ref-counting. Putting a lock-free std::shared_ptr requirement / guarantee in the standard would theoretically restrict which platforms could support a conforming C++11 implementation. I think that's the reason, not that locking might be actually better on a normal platform where lock-free was possible.

      – Peter Cordes
      Nov 26 '18 at 20:57
















    5















    (1) Regarding size: How can I programatically find the exact size of the control block for a std::shared_ptr?




    There is no way. It's not directly accessible.




    (2) Regarding logic: Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?




    Absolutely not. Lock-free implementations are not always better than implementations that use locks. Having an additional constraint, at best, doesn't make the implementation worse but it cannot possibly make the implementation better.



    Consider two equally competent programmers each doing their best to implement shared_ptr. One must produce a lock-free implementation. The other is completely free to use their best judgment. There is simply no way the one that must produce a lock-free implementation can produce a better implementation all other things being equal. At best, a lock-free implementation is best and they'll both produce one. At worse, on this platform a lock-free implementation has huge disadvantages and one implementer must use one. Yuck.






    share|improve this answer
























    • Do note that the Boost quote is not "completely lock-free"; it's merely lock-free "on most common platforms".

      – Nicol Bolas
      Nov 26 '18 at 4:30






    • 2





      @NicolBolas Yeah. It's probably just a statement about what the implementers judged to be best. It's hard to imagine how you would ever need or want a lock on any modern platform -- there's no case where any thread would ever need to wait for any other thread.

      – David Schwartz
      Nov 26 '18 at 4:34






    • 3





      C++11 only requires lock-free std::atomic_flag, which is enough to build a lock from, but not enough for lockless ref-counting. Putting a lock-free std::shared_ptr requirement / guarantee in the standard would theoretically restrict which platforms could support a conforming C++11 implementation. I think that's the reason, not that locking might be actually better on a normal platform where lock-free was possible.

      – Peter Cordes
      Nov 26 '18 at 20:57














    5












    5








    5








    (1) Regarding size: How can I programatically find the exact size of the control block for a std::shared_ptr?




    There is no way. It's not directly accessible.




    (2) Regarding logic: Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?




    Absolutely not. Lock-free implementations are not always better than implementations that use locks. Having an additional constraint, at best, doesn't make the implementation worse but it cannot possibly make the implementation better.



    Consider two equally competent programmers each doing their best to implement shared_ptr. One must produce a lock-free implementation. The other is completely free to use their best judgment. There is simply no way the one that must produce a lock-free implementation can produce a better implementation all other things being equal. At best, a lock-free implementation is best and they'll both produce one. At worse, on this platform a lock-free implementation has huge disadvantages and one implementer must use one. Yuck.






    share|improve this answer














    (1) Regarding size: How can I programatically find the exact size of the control block for a std::shared_ptr?




    There is no way. It's not directly accessible.




    (2) Regarding logic: Additionally, boost::shared_ptr mentions that they are completely lock-free with respect to changes in the control block.(Starting with Boost release 1.33.0, shared_ptr uses a lock-free implementation on most common platforms.) I don't think std::shared_ptr follows the same - is this planned for any future C++ version? Doesn't this also mean that boost::shared_ptr is a better idea for multithreaded cases?




    Absolutely not. Lock-free implementations are not always better than implementations that use locks. Having an additional constraint, at best, doesn't make the implementation worse but it cannot possibly make the implementation better.



    Consider two equally competent programmers each doing their best to implement shared_ptr. One must produce a lock-free implementation. The other is completely free to use their best judgment. There is simply no way the one that must produce a lock-free implementation can produce a better implementation all other things being equal. At best, a lock-free implementation is best and they'll both produce one. At worse, on this platform a lock-free implementation has huge disadvantages and one implementer must use one. Yuck.







    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Nov 26 '18 at 4:26









    David SchwartzDavid Schwartz

    139k14145230




    139k14145230













    • Do note that the Boost quote is not "completely lock-free"; it's merely lock-free "on most common platforms".

      – Nicol Bolas
      Nov 26 '18 at 4:30






    • 2





      @NicolBolas Yeah. It's probably just a statement about what the implementers judged to be best. It's hard to imagine how you would ever need or want a lock on any modern platform -- there's no case where any thread would ever need to wait for any other thread.

      – David Schwartz
      Nov 26 '18 at 4:34






    • 3





      C++11 only requires lock-free std::atomic_flag, which is enough to build a lock from, but not enough for lockless ref-counting. Putting a lock-free std::shared_ptr requirement / guarantee in the standard would theoretically restrict which platforms could support a conforming C++11 implementation. I think that's the reason, not that locking might be actually better on a normal platform where lock-free was possible.

      – Peter Cordes
      Nov 26 '18 at 20:57



















    • Do note that the Boost quote is not "completely lock-free"; it's merely lock-free "on most common platforms".

      – Nicol Bolas
      Nov 26 '18 at 4:30






    • 2





      @NicolBolas Yeah. It's probably just a statement about what the implementers judged to be best. It's hard to imagine how you would ever need or want a lock on any modern platform -- there's no case where any thread would ever need to wait for any other thread.

      – David Schwartz
      Nov 26 '18 at 4:34






    • 3





      C++11 only requires lock-free std::atomic_flag, which is enough to build a lock from, but not enough for lockless ref-counting. Putting a lock-free std::shared_ptr requirement / guarantee in the standard would theoretically restrict which platforms could support a conforming C++11 implementation. I think that's the reason, not that locking might be actually better on a normal platform where lock-free was possible.

      – Peter Cordes
      Nov 26 '18 at 20:57

















    Do note that the Boost quote is not "completely lock-free"; it's merely lock-free "on most common platforms".

    – Nicol Bolas
    Nov 26 '18 at 4:30





    Do note that the Boost quote is not "completely lock-free"; it's merely lock-free "on most common platforms".

    – Nicol Bolas
    Nov 26 '18 at 4:30




    2




    2





    @NicolBolas Yeah. It's probably just a statement about what the implementers judged to be best. It's hard to imagine how you would ever need or want a lock on any modern platform -- there's no case where any thread would ever need to wait for any other thread.

    – David Schwartz
    Nov 26 '18 at 4:34





    @NicolBolas Yeah. It's probably just a statement about what the implementers judged to be best. It's hard to imagine how you would ever need or want a lock on any modern platform -- there's no case where any thread would ever need to wait for any other thread.

    – David Schwartz
    Nov 26 '18 at 4:34




    3




    3





    C++11 only requires lock-free std::atomic_flag, which is enough to build a lock from, but not enough for lockless ref-counting. Putting a lock-free std::shared_ptr requirement / guarantee in the standard would theoretically restrict which platforms could support a conforming C++11 implementation. I think that's the reason, not that locking might be actually better on a normal platform where lock-free was possible.

    – Peter Cordes
    Nov 26 '18 at 20:57





    C++11 only requires lock-free std::atomic_flag, which is enough to build a lock from, but not enough for lockless ref-counting. Putting a lock-free std::shared_ptr requirement / guarantee in the standard would theoretically restrict which platforms could support a conforming C++11 implementation. I think that's the reason, not that locking might be actually better on a normal platform where lock-free was possible.

    – Peter Cordes
    Nov 26 '18 at 20:57













    5














    The control block is not exposed. In implementations I have read it is dynamic in size to store the deleter contiguously (and/or, in the case of make shared, the object itself).



    In general it contains at least 3 pointer-size fields - weak, strong count, and deleter invoker.



    At least one implementation relies on RTTI; others do not.



    Operations on the count use atomic operations in the implementations I have read; note that C++ does not require atomic operatins to all be lock free (I believe a platform that doesn't have pointer-size lock-free operations can be a conforming C++ platform).



    Their state is are consistent with each other and themselves, but no attempt to make them consistent with object state occurs. This is why using raw shared ptrs as copy on write pImpls may be error prone on some platforms.






    share|improve this answer



















    • 1





      Yup, only std::atomic_flag is guaranteed lock-free. Good implementations on targets that can do it will use lock-free atomics for shared_ptr and atomic<T> up to a width the HW can support, of course.

      – Peter Cordes
      Nov 26 '18 at 21:00


















    5














    The control block is not exposed. In implementations I have read it is dynamic in size to store the deleter contiguously (and/or, in the case of make shared, the object itself).



    In general it contains at least 3 pointer-size fields - weak, strong count, and deleter invoker.



    At least one implementation relies on RTTI; others do not.



    Operations on the count use atomic operations in the implementations I have read; note that C++ does not require atomic operatins to all be lock free (I believe a platform that doesn't have pointer-size lock-free operations can be a conforming C++ platform).



    Their state is are consistent with each other and themselves, but no attempt to make them consistent with object state occurs. This is why using raw shared ptrs as copy on write pImpls may be error prone on some platforms.






    share|improve this answer



















    • 1





      Yup, only std::atomic_flag is guaranteed lock-free. Good implementations on targets that can do it will use lock-free atomics for shared_ptr and atomic<T> up to a width the HW can support, of course.

      – Peter Cordes
      Nov 26 '18 at 21:00
















    5












    5








    5







    The control block is not exposed. In implementations I have read it is dynamic in size to store the deleter contiguously (and/or, in the case of make shared, the object itself).



    In general it contains at least 3 pointer-size fields - weak, strong count, and deleter invoker.



    At least one implementation relies on RTTI; others do not.



    Operations on the count use atomic operations in the implementations I have read; note that C++ does not require atomic operatins to all be lock free (I believe a platform that doesn't have pointer-size lock-free operations can be a conforming C++ platform).



    Their state is are consistent with each other and themselves, but no attempt to make them consistent with object state occurs. This is why using raw shared ptrs as copy on write pImpls may be error prone on some platforms.






    share|improve this answer













    The control block is not exposed. In implementations I have read it is dynamic in size to store the deleter contiguously (and/or, in the case of make shared, the object itself).



    In general it contains at least 3 pointer-size fields - weak, strong count, and deleter invoker.



    At least one implementation relies on RTTI; others do not.



    Operations on the count use atomic operations in the implementations I have read; note that C++ does not require atomic operatins to all be lock free (I believe a platform that doesn't have pointer-size lock-free operations can be a conforming C++ platform).



    Their state is are consistent with each other and themselves, but no attempt to make them consistent with object state occurs. This is why using raw shared ptrs as copy on write pImpls may be error prone on some platforms.







    share|improve this answer












    share|improve this answer



    share|improve this answer










    answered Nov 26 '18 at 4:23









    Yakk - Adam NevraumontYakk - Adam Nevraumont

    188k21199384




    188k21199384








    • 1





      Yup, only std::atomic_flag is guaranteed lock-free. Good implementations on targets that can do it will use lock-free atomics for shared_ptr and atomic<T> up to a width the HW can support, of course.

      – Peter Cordes
      Nov 26 '18 at 21:00
















    • 1





      Yup, only std::atomic_flag is guaranteed lock-free. Good implementations on targets that can do it will use lock-free atomics for shared_ptr and atomic<T> up to a width the HW can support, of course.

      – Peter Cordes
      Nov 26 '18 at 21:00










    1




    1





    Yup, only std::atomic_flag is guaranteed lock-free. Good implementations on targets that can do it will use lock-free atomics for shared_ptr and atomic<T> up to a width the HW can support, of course.

    – Peter Cordes
    Nov 26 '18 at 21:00







    Yup, only std::atomic_flag is guaranteed lock-free. Good implementations on targets that can do it will use lock-free atomics for shared_ptr and atomic<T> up to a width the HW can support, of course.

    – Peter Cordes
    Nov 26 '18 at 21:00













    1














    (1)
    Of course it is best to check implementation, however you still may make some checks from your program.



    Control block is allocated dynamically, so to determine its size you may overload new operator.



    Then what you may also check is if std::make_shared provides you with some optimization of control block size.
    In proper implementation I would expect that this will make two allocations (objectA and control block):



    std::shared_ptr<A> i(new A());


    However this will make only one allocation (and then objectA initialized with placement new):



    auto a = std::make_shared<A>();


    Consider following example:



    #include <iostream>
    #include <memory>

    void * operator new(size_t size)
    {
    std::cout << "Requested allocation: " << size << std::endl;
    void * p = malloc(size);
    return p;
    }

    class A {};

    class B
    {
    int a[8];
    };

    int main()
    {
    std::cout << "Sizeof int: " << sizeof(int) << ", A(empty): " << sizeof(A) << ", B(8 ints): " << sizeof(B) << std::endl;
    {
    std::cout << "Just new:" << std::endl;
    std::cout << "- int:" << std::endl;
    std::shared_ptr<int> i(new int());
    std::cout << "- A(empty):" << std::endl;
    std::shared_ptr<A> a(new A());
    std::cout << "- B(8 ints):" << std::endl;
    std::shared_ptr<B> b(new B());
    }
    {
    std::cout << "Make shared:" << std::endl;
    std::cout << "- int:" << std::endl;
    auto i = std::make_shared<int>();
    std::cout << "- A(empty):" << std::endl;
    auto a = std::make_shared<A>();
    std::cout << "- B(8 ints):" << std::endl;
    auto b = std::make_shared<B>();
    }
    }


    The output I received (of course it is hw architecture and compiler specific):



    Sizeof int: 4, A(empty): 1, B(8 ints): 32
    Just new:
    - int:
    Requested allocation: 4
    Requested allocation: 24


    First allocation for int - 4 bytes, next one for control block - 24 bytes.



    - A(empty):
    Requested allocation: 1
    Requested allocation: 24
    - B(8 ints):
    Requested allocation: 32
    Requested allocation: 24


    Looks that control block is (most probably) 24 bytes.



    Here is why to use make_shared:



    Make shared:
    - int:
    Requested allocation: 24


    Only one allocation, int + control block = 24 bytes, less then before.



    - A(empty):
    Requested allocation: 24
    - B(8 ints):
    Requested allocation: 48


    Here one could expect 56 (32+24), but it looks that implementation is optimized. If you use make_shared - pointer to actual object is not needed in control block and its size is only 16 bytes.



    Other possibility to check the size of control block is to:



    std::cout<< sizeof(std::enable_shared_from_this<int>);


    In my case:



    16


    So I would say that the size of control block in my case is 16-24 bytes, depending on how it was created.






    share|improve this answer




























      1














      (1)
      Of course it is best to check implementation, however you still may make some checks from your program.



      Control block is allocated dynamically, so to determine its size you may overload new operator.



      Then what you may also check is if std::make_shared provides you with some optimization of control block size.
      In proper implementation I would expect that this will make two allocations (objectA and control block):



      std::shared_ptr<A> i(new A());


      However this will make only one allocation (and then objectA initialized with placement new):



      auto a = std::make_shared<A>();


      Consider following example:



      #include <iostream>
      #include <memory>

      void * operator new(size_t size)
      {
      std::cout << "Requested allocation: " << size << std::endl;
      void * p = malloc(size);
      return p;
      }

      class A {};

      class B
      {
      int a[8];
      };

      int main()
      {
      std::cout << "Sizeof int: " << sizeof(int) << ", A(empty): " << sizeof(A) << ", B(8 ints): " << sizeof(B) << std::endl;
      {
      std::cout << "Just new:" << std::endl;
      std::cout << "- int:" << std::endl;
      std::shared_ptr<int> i(new int());
      std::cout << "- A(empty):" << std::endl;
      std::shared_ptr<A> a(new A());
      std::cout << "- B(8 ints):" << std::endl;
      std::shared_ptr<B> b(new B());
      }
      {
      std::cout << "Make shared:" << std::endl;
      std::cout << "- int:" << std::endl;
      auto i = std::make_shared<int>();
      std::cout << "- A(empty):" << std::endl;
      auto a = std::make_shared<A>();
      std::cout << "- B(8 ints):" << std::endl;
      auto b = std::make_shared<B>();
      }
      }


      The output I received (of course it is hw architecture and compiler specific):



      Sizeof int: 4, A(empty): 1, B(8 ints): 32
      Just new:
      - int:
      Requested allocation: 4
      Requested allocation: 24


      First allocation for int - 4 bytes, next one for control block - 24 bytes.



      - A(empty):
      Requested allocation: 1
      Requested allocation: 24
      - B(8 ints):
      Requested allocation: 32
      Requested allocation: 24


      Looks that control block is (most probably) 24 bytes.



      Here is why to use make_shared:



      Make shared:
      - int:
      Requested allocation: 24


      Only one allocation, int + control block = 24 bytes, less then before.



      - A(empty):
      Requested allocation: 24
      - B(8 ints):
      Requested allocation: 48


      Here one could expect 56 (32+24), but it looks that implementation is optimized. If you use make_shared - pointer to actual object is not needed in control block and its size is only 16 bytes.



      Other possibility to check the size of control block is to:



      std::cout<< sizeof(std::enable_shared_from_this<int>);


      In my case:



      16


      So I would say that the size of control block in my case is 16-24 bytes, depending on how it was created.






      share|improve this answer


























        1












        1








        1







        (1)
        Of course it is best to check implementation, however you still may make some checks from your program.



        Control block is allocated dynamically, so to determine its size you may overload new operator.



        Then what you may also check is if std::make_shared provides you with some optimization of control block size.
        In proper implementation I would expect that this will make two allocations (objectA and control block):



        std::shared_ptr<A> i(new A());


        However this will make only one allocation (and then objectA initialized with placement new):



        auto a = std::make_shared<A>();


        Consider following example:



        #include <iostream>
        #include <memory>

        void * operator new(size_t size)
        {
        std::cout << "Requested allocation: " << size << std::endl;
        void * p = malloc(size);
        return p;
        }

        class A {};

        class B
        {
        int a[8];
        };

        int main()
        {
        std::cout << "Sizeof int: " << sizeof(int) << ", A(empty): " << sizeof(A) << ", B(8 ints): " << sizeof(B) << std::endl;
        {
        std::cout << "Just new:" << std::endl;
        std::cout << "- int:" << std::endl;
        std::shared_ptr<int> i(new int());
        std::cout << "- A(empty):" << std::endl;
        std::shared_ptr<A> a(new A());
        std::cout << "- B(8 ints):" << std::endl;
        std::shared_ptr<B> b(new B());
        }
        {
        std::cout << "Make shared:" << std::endl;
        std::cout << "- int:" << std::endl;
        auto i = std::make_shared<int>();
        std::cout << "- A(empty):" << std::endl;
        auto a = std::make_shared<A>();
        std::cout << "- B(8 ints):" << std::endl;
        auto b = std::make_shared<B>();
        }
        }


        The output I received (of course it is hw architecture and compiler specific):



        Sizeof int: 4, A(empty): 1, B(8 ints): 32
        Just new:
        - int:
        Requested allocation: 4
        Requested allocation: 24


        First allocation for int - 4 bytes, next one for control block - 24 bytes.



        - A(empty):
        Requested allocation: 1
        Requested allocation: 24
        - B(8 ints):
        Requested allocation: 32
        Requested allocation: 24


        Looks that control block is (most probably) 24 bytes.



        Here is why to use make_shared:



        Make shared:
        - int:
        Requested allocation: 24


        Only one allocation, int + control block = 24 bytes, less then before.



        - A(empty):
        Requested allocation: 24
        - B(8 ints):
        Requested allocation: 48


        Here one could expect 56 (32+24), but it looks that implementation is optimized. If you use make_shared - pointer to actual object is not needed in control block and its size is only 16 bytes.



        Other possibility to check the size of control block is to:



        std::cout<< sizeof(std::enable_shared_from_this<int>);


        In my case:



        16


        So I would say that the size of control block in my case is 16-24 bytes, depending on how it was created.






        share|improve this answer













        (1)
        Of course it is best to check implementation, however you still may make some checks from your program.



        Control block is allocated dynamically, so to determine its size you may overload new operator.



        Then what you may also check is if std::make_shared provides you with some optimization of control block size.
        In proper implementation I would expect that this will make two allocations (objectA and control block):



        std::shared_ptr<A> i(new A());


        However this will make only one allocation (and then objectA initialized with placement new):



        auto a = std::make_shared<A>();


        Consider following example:



        #include <iostream>
        #include <memory>

        void * operator new(size_t size)
        {
        std::cout << "Requested allocation: " << size << std::endl;
        void * p = malloc(size);
        return p;
        }

        class A {};

        class B
        {
        int a[8];
        };

        int main()
        {
        std::cout << "Sizeof int: " << sizeof(int) << ", A(empty): " << sizeof(A) << ", B(8 ints): " << sizeof(B) << std::endl;
        {
        std::cout << "Just new:" << std::endl;
        std::cout << "- int:" << std::endl;
        std::shared_ptr<int> i(new int());
        std::cout << "- A(empty):" << std::endl;
        std::shared_ptr<A> a(new A());
        std::cout << "- B(8 ints):" << std::endl;
        std::shared_ptr<B> b(new B());
        }
        {
        std::cout << "Make shared:" << std::endl;
        std::cout << "- int:" << std::endl;
        auto i = std::make_shared<int>();
        std::cout << "- A(empty):" << std::endl;
        auto a = std::make_shared<A>();
        std::cout << "- B(8 ints):" << std::endl;
        auto b = std::make_shared<B>();
        }
        }


        The output I received (of course it is hw architecture and compiler specific):



        Sizeof int: 4, A(empty): 1, B(8 ints): 32
        Just new:
        - int:
        Requested allocation: 4
        Requested allocation: 24


        First allocation for int - 4 bytes, next one for control block - 24 bytes.



        - A(empty):
        Requested allocation: 1
        Requested allocation: 24
        - B(8 ints):
        Requested allocation: 32
        Requested allocation: 24


        Looks that control block is (most probably) 24 bytes.



        Here is why to use make_shared:



        Make shared:
        - int:
        Requested allocation: 24


        Only one allocation, int + control block = 24 bytes, less then before.



        - A(empty):
        Requested allocation: 24
        - B(8 ints):
        Requested allocation: 48


        Here one could expect 56 (32+24), but it looks that implementation is optimized. If you use make_shared - pointer to actual object is not needed in control block and its size is only 16 bytes.



        Other possibility to check the size of control block is to:



        std::cout<< sizeof(std::enable_shared_from_this<int>);


        In my case:



        16


        So I would say that the size of control block in my case is 16-24 bytes, depending on how it was created.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered Dec 22 '18 at 21:04









        wpfmanwpfman

        211




        211






























            draft saved

            draft discarded




















































            Thanks for contributing an answer to Stack Overflow!


            • Please be sure to answer the question. Provide details and share your research!

            But avoid



            • Asking for help, clarification, or responding to other answers.

            • Making statements based on opinion; back them up with references or personal experience.


            To learn more, see our tips on writing great answers.




            draft saved


            draft discarded














            StackExchange.ready(
            function () {
            StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53474539%2fregarding-shared-ptr-reference-count-block%23new-answer', 'question_page');
            }
            );

            Post as a guest















            Required, but never shown





















































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown

































            Required, but never shown














            Required, but never shown












            Required, but never shown







            Required, but never shown







            Popular posts from this blog

            Costa Masnaga

            Fotorealismo

            Sidney Franklin