{"id":108626,"date":"2023-08-21T07:00:00","date_gmt":"2023-08-21T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108626"},"modified":"2023-08-21T06:51:11","modified_gmt":"2023-08-21T13:51:11","slug":"20230821-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230821-00\/?p=108626","title":{"rendered":"Inside STL: The different types of shared pointer control blocks"},"content":{"rendered":"<p>We saw earlier that <a title=\"Inside STL: Smart pointers\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230814-00\/?p=108597\"> C++ standard library shared pointers use a control block<\/a> to manage the object lifetime.<\/p>\n<pre>struct control_block\r\n{\r\n    virtual void Dispose() = 0;\r\n    virtual void Delete() = 0;\r\n    std::atomic&lt;unsigned long&gt; shareds;\r\n    std::atomic&lt;unsigned long&gt; refs;\r\n};\r\n<\/pre>\n<p>The control block has pure virtual methods, so it is up to derived classes to establish how to dispose and delete the control block.<\/p>\n<p>If you ask a <code>shared_ptr<\/code> to take responsibility for an already-constructed pointer, then you get this:<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct separate_control_block : control_block\r\n{\r\n    virtual void Destroy() noexcept override\r\n    {\r\n        delete ptr;\r\n    }\r\n    virtual void Delete() noexcept override\r\n    {\r\n        delete this;\r\n    }\r\n    T* ptr;\r\n};\r\n<\/pre>\n<p>Added to the basic control block is a pointer to the managed object, which is <code>delete<\/code>d when the last strong reference goes away.<\/p>\n<p>If you use <code>make_<wbr \/>shared<\/code> or <code>allocate_<wbr \/>shared<\/code>, then the control block and the managed object are placed in the same allocation. In that case, the control block looks like this:<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct combined_control_block : control_block\r\n{\r\n    virtual void Destroy() noexcept override\r\n    {\r\n        ptr()-&gt;~T();\r\n    }\r\n    virtual void Delete() noexcept override\r\n    {\r\n        delete this;\r\n    }\r\n    T* ptr() { return reinterpret_cast&lt;T*&gt;(buffer); }\r\n\r\n    \/\/ This buffer holds a \"T\"\r\n    [[alignas(T)]] char buffer[sizeof(T)];\r\n};\r\n<\/pre>\n<p>Added to the basic control block is a buffer suitable for holding a <code>T<\/code> object. When the <code>shared_ptr<\/code> is created, a <code>T<\/code> is placement-constructed in that buffer, and when the last strong reference goes away (<code>Destroy()<\/code>), it is destructed. Stephan T. Lavavej calls this <a href=\"https:\/\/www.reddit.com\/r\/cpp\/comments\/15sjmfa\/inside_stl_the_shared_ptr_constructor_vs_make\/jwepoxh\/\"> the &#8220;We know where you live&#8221; optimization<\/a> because the control block doesn&#8217;t need to store an explicit pointer to the buffer; it can derive it on the fly.<\/p>\n<p>The reality is a little more complicated due to the need to store a deleter and possibly an allocator, but those are typically zero-length objects, so they get stored in a compressed pair with the other members.<\/p>\n<p>In practice, when debugging, you don&#8217;t need to look past the reference counts in the <code>control_block<\/code>. The thing you really care about in the <code>shared_ptr<\/code> is the pointed-to object. If you ever look at the control block, it&#8217;s just to check whether there are any active strong references.<\/p>\n<p><b>Bonus chatter<\/b>: For C++20 <code>make_shared&lt;T[]&gt;<\/code>, there&#8217;s another version of the control block that also has a <code>count<\/code> member which specifies how many objects are in the storage.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Well, some of them, at least.<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-108626","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Well, some of them, at least.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108626","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=108626"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108626\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=108626"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108626"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108626"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}