{"id":108597,"date":"2023-08-14T07:00:00","date_gmt":"2023-08-14T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108597"},"modified":"2023-08-14T06:53:53","modified_gmt":"2023-08-14T13:53:53","slug":"20230814-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230814-00\/?p=108597","title":{"rendered":"Inside STL: Smart pointers"},"content":{"rendered":"<p>The C++ standard library comes with a few smart pointer types.<\/p>\n<p>The simplest one is <code>unique_ptr<\/code>: This class babysits a raw pointer and remembers to delete it at destruction (in an appropriate manner). Dumping the contents of a <code>unique_ptr<\/code> is just looking at the raw pointer inside.<\/p>\n<p>The complication is that there is also a deleter object in the <code>unique_ptr<\/code>. This deleter object is usually an empty class, so it is stored as part of a compressed pair.<\/p>\n<p>The Visual Studio debugger has a visualizer that understands <code>unique_ptr<\/code>, but here&#8217;s what it looks like at a low level in the Microsoft implementation:<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&lt; ?? p\r\nclass std::unique_ptr&lt;S,std::default_delete&lt;S&gt; &gt;\r\n   +0x000 _Mypair          : std::_Compressed_pair&lt;std::default_delete&lt;S&gt;,S *,1&gt;\r\n0:000&lt; ?? p._Mypair\r\nclass std::_Compressed_pair&lt;std::default_delete&lt;S&gt;,S *,1&gt;\r\n   +0x000 _Myval2          : 0x0000020a`11f08490 S\r\n0:000&lt; ?? p._Mypair._Myval2\r\nstruct S * 0x0000020a`11f08490 \u2190 here is the unique object\r\n   +0x000 a                : 0n42\r\n<\/pre>\n<p>Next up is are the buddies <code>shared_ptr<\/code> and <code>weak_ptr<\/code>. These guys are just alternate policies around the same design, which centers around a &#8220;control block&#8221;.<\/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; weaks;\r\n};\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct shared_ptr\r\n{\r\n    T* object;\r\n    control_block* control;\r\n};\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct weak_ptr\r\n{\r\n    T* object;\r\n    control_block* control;\r\n};\r\n<\/pre>\n<p>The control block keeps track of how many shared and weak pointers exist, but in a somewhat unusual way. The na\u00efve approach would be to have <code>shareds<\/code> and <code>weaks<\/code> count the number of shared and weak pointers, respectively. However, this ends up being more complicated because detecting when the last pointer to the control block has been destroyed would require us to perform atomic checks on <i>two<\/i> member variables simultaneously.<\/p>\n<p>You can solve this by having <code>weaks<\/code> be the number of shared pointers <i>plus<\/i> the number of weak pointers. This is the total number of things keeping the control block alive. A test of a single atomic variable will tell you when the last pointer has been destroyed.<\/p>\n<p>Here&#8217;s how the fundamental operations play out with this design:<\/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>Create the first shared pointer: Create a new control block with <code>shareds<\/code> and <code>refs<\/code> both initialized to 1.<\/p>\n<p>Copy a nonempty weak pointer: Atomically increment the <code>refs<\/code>. (We know that the <code>refs<\/code> is at least one, because it includes the weak pointer we are copying from.)<\/p>\n<p>Copy a nonempty shared pointer: Atomically increment both <code>shareds<\/code> and <code>refs<\/code>. (We know that both are already at least one, because they includes the shared pointer we are copying from.)<\/p>\n<p>Convert a nonempty shared pointer to a weak pointer: Atomically increment the <code>refs<\/code>. (We know that the <code>refs<\/code> is at least one, because it includes the weak pointer we are copying from.)<\/p>\n<p>Convert a nonempty weak pointer to a shared pointer: Atomically increment the <code>shareds<\/code>, but only if it was originally nonzero.\u00b9 If successful, then also increment the <code>refs<\/code>. If <code>shareds<\/code> was zero, then produce an empty shared pointer instead.<\/p>\n<p>Destruct a nonempty weak pointer: Atomically decrement the <code>refs<\/code>. If this decrements to zero, then call <code>Delete()<\/code> to delete the control block.<\/p>\n<p>Destruct a nonempty shared pointer: Atomically decrement the <code>shareds<\/code>. If this decrements to zero, then call <code>Dispose()<\/code> to destruct the managed object. Next, atomically decrement the <code>refs<\/code>. If this also decrements to zero, then call <code>Delete()<\/code> to delete the control block.<\/p>\n<p>This design works, but it turns out you can be more clever about it: Instead of counting all shared pointers toward the <code>refs<\/code>, count only one shared pointer toward the <code>refs<\/code>, and update <code>refs<\/code> only when the number of <code>shareds<\/code> transitions between 1 and 0.<\/p>\n<p>So here&#8217;s our optimized version:<\/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; weaks;\r\n};\r\n<\/pre>\n<p>Create the first shared pointer: Create a new control block with <code>shareds<\/code> and <code>weaks<\/code> both initialized to 1.<\/p>\n<p>Copy a nonempty weak pointer: Atomically increment the <code>weaks<\/code>. (We know that the <code>weaks<\/code> is at least one, because it includes the weak pointer we are copying from.)<\/p>\n<p>Copy a nonempty shared pointer: Atomically increment the <code>shareds<\/code>. (We know that the <code>shareds<\/code> is at least one, because it includes the shared pointer we are copying from.)<\/p>\n<p>Convert a nonempty shared pointer to a weak pointer: Atomically increment the <code>weaks<\/code>. (We know that the <code>weaks<\/code> is at least one, because it includes the weak pointer we are copying from.)<\/p>\n<p>Convert a nonempty weak pointer to a shared pointer: Atomically increment the <code>shareds<\/code>, but only if it was originally nonzero.\u00b9 If it was zero, then produce an empty shared pointer instead.<\/p>\n<p>Destruct a nonempty weak pointer: Atomically decrement the <code>weaks<\/code>. If this decrements to zero, then call <code>Delete()<\/code> to delete the control block.<\/p>\n<p>Destruct a nonempty shared pointer: Atomically decrement the <code>shareds<\/code>. If this decrements to zero, then there&#8217;s extra work to do: Destroy the associated object and decrement the <code>weaks<\/code>. If <code>weaks<\/code> also decrements to zero, then call <code>Delete()<\/code> to delete the control block.<\/p>\n<p>There&#8217;s a lesser-known feature of shared and weak pointers: The <i>aliasing contructor<\/i>. This lets you create a <code>shared_ptr<\/code> that dereferences to an object that is different from the one managed by the control block. For example, you can do this:<\/p>\n<pre>struct S { int a = 42; int b = 99; };\r\n\r\nauto s = std::make_shared&lt;S&gt;();\r\nauto i = std::shared_ptr&lt;int&gt;(s, &amp;s-&gt;b);\r\n<\/pre>\n<p>The shared pointer <code>i<\/code> points to the <code>b<\/code> member of the shared <code>S<\/code> object, but using the lifetime of the <code>S<\/code> object controlled by the pre-existing shared pointer <code>s<\/code>.<\/p>\n<p>Internally, you create an aliasing shared pointer by using the same control block as the source shared pointer (incrementing the <code>shareds<\/code>, naturally), but setting the object pointer to the second constructor parameter.<\/p>\n<p>Again, the Visual Studio debugger understands all of this and gives you a nice visualization. If you&#8217;re debugging at a lower level, then here&#8217;s what it looks like in the debugger:<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? p2\r\nclass std::shared_ptr&lt;S&gt;\r\n   +0x000 _Ptr             : 0x0000020a`11f084e0 S\r\n   +0x008 _Rep             : 0x0000020a`11f084d0 std::_Ref_count_base\r\n0:000&gt; ?? p2._Ptr\r\nstruct S * 0x0000020a`11f084e0\r\n   +0x000 a                : 0n42\r\n0:000&gt; ?? p2._Rep\r\nclass std::_Ref_count_base * 0x0000020a`11f084d0\r\n   +0x000 __VFN_table : 0x00007ff7`9e788758\r\n   +0x008 _Uses            : 1\r\n   +0x00c _Weaks           : 1\r\n<\/pre>\n<p>Here&#8217;s how the names we used map to the names in the Microsoft implementation of the C++ standard library:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Our name<\/th>\n<th>MSVC name<\/th>\n<th>Notes<\/th>\n<\/tr>\n<tr>\n<td><code>object<\/code><\/td>\n<td><code>_Ptr<\/code><\/td>\n<td>Pointer to managed object<\/td>\n<\/tr>\n<tr>\n<td><code>control<\/code><\/td>\n<td><code>_Rep<\/code><\/td>\n<td>Pointer to control block<\/td>\n<\/tr>\n<tr>\n<td><code>shareds<\/code><\/td>\n<td><code>_Uses<\/code><\/td>\n<td>Number of shared pointers<\/td>\n<\/tr>\n<tr>\n<td><code>weaks<\/code><\/td>\n<td><code>_Weaks<\/code><\/td>\n<td>Number of weak pointers<br \/>\n+ 1 if there are any shared pointers<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>When inspecting a <code>shared_ptr<\/code>, you can just follow the <code>_Ptr<\/code> to get to the managed object. You know that the pointer is valid because this is a shared pointer which keeps the object alive. (If the <code>_Ptr<\/code> is <code>nullptr<\/code>, then the shared pointer is empty.)<\/p>\n<p>A weak pointer looks pretty much identical in the debugger:<\/p>\n<pre style=\"white-space: pre-wrap;\">0:000&gt; ?? p3\r\nclass std::weak_ptr&lt;S&gt;\r\n   +0x000 _Ptr             : 0x0000020a`11f084e0 S\r\n   +0x008 _Rep             : 0x0000020a`11f084d0 std::_Ref_count_base\r\n0:000&gt; ?? p3._Ptr\r\nstruct S * 0x0000020a`11f084e0\r\n   +0x000 a                : 0n42\r\n0:000&gt; ?? p3._Rep\r\nclass std::_Ref_count_base * 0x0000020a`11f084d0\r\n   +0x000 __VFN_table : 0x00007ff7`9e788758\r\n   +0x008 _Uses            : 1\r\n   +0x00c _Weaks           : 2\r\n<\/pre>\n<p>When inspecting a weak pointer in the debugger, you have to check the control block&#8217;s shared pointer count to know whether or not the object is still valid. If the shared pointer count is zero, then the weak pointer has expired, and the object pointer points to freed memory.<\/p>\n<p>Next time, we&#8217;ll look at <code>std::<wbr \/>make_<wbr \/>shared<\/code> and <code>std::<wbr \/>enable_<wbr \/>shared_<wbr \/>from_<wbr \/>this<\/code>.<\/p>\n<p><b>Bonus viewing<\/b>: <a href=\"https:\/\/learn.microsoft.com\/en-us\/shows\/c9-lectures-stephan-t-lavavej-standard-template-library-stl-\/3-of-n\"> Stephan T. Lavavej&#8217;s lecture on <code>shared_ptr<\/code> and <code>unique_ptr<\/code><\/a>. He also gave an advanced lecture, which I cannot find.<\/p>\n<p>\u00b9 This can be accomplished by a <code>compare_<wbr \/>exchange<\/code> loop. <a title=\"How do I choose between the strong and weak versions of compare-exchange?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20180330-00\/?p=98395\"> Given the scenario<\/a>, this would be better served by a <code>compare_<wbr \/>exchange_<wbr \/>weak<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Simple pointers or more complicated pointers.<\/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-108597","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Simple pointers or more complicated pointers.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108597","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=108597"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108597\/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=108597"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108597"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108597"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}