{"id":108611,"date":"2023-08-17T07:00:00","date_gmt":"2023-08-17T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=108611"},"modified":"2023-08-17T06:53:30","modified_gmt":"2023-08-17T13:53:30","slug":"20230817-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20230817-00\/?p=108611","title":{"rendered":"What it means when you convert between different <CODE>shared_ptr<\/CODE>s"},"content":{"rendered":"<p>The C++ <code>shared_ptr<\/code> manages a reference-counted pointer. Usually, it&#8217;s a pointer to an object that will be <code>delete<\/code>&#8216;d when the last reference expires. But it doesn&#8217;t have to be that way.<\/p>\n<p>Recall that a <code>shared_ptr<\/code> is really two pointers.<\/p>\n<ul>\n<li>A pointer to a <i>control block<\/i> which manages the shared and weak reference counts, and which destroys an object (commonly known as the <i>managed object<\/i>) when the shared reference count drops to zero.<\/li>\n<li>A pointer to return from the <code>get()<\/code> method, commonly know as the <i>stored pointer<\/i>.<\/li>\n<\/ul>\n<p>Most of the time, the stored pointer points to the managed object, because that what you get when you construct a <code>shared_ptr<\/code>s from a raw pointer or when you call <code>make_shared<\/code>. But what is the use case for a <code>shared_ptr<\/code> where the managed object and the stored pointer are different?<\/p>\n<p>You may want to have a <code>shared_ptr<\/code> whose <code>get()<\/code> returns a pointer to a sub-object of another large object. In that case, the managed object is the larger object, and the stored pointer is to the sub-object.<\/p>\n<pre>struct Sample\r\n{\r\n    int value1;\r\n    int value2;\r\n};\r\n\r\nvoid consume(std::shared_ptr&lt;int&gt; pint);\r\n\r\nstd::shared_ptr&lt;Sample&gt; p = std::make_shared&lt;Sample&gt;();\r\nconsume(std::shared_ptr&lt;int&gt;(p, &amp;p-&gt;value1));\r\n\r\n\/\/ Or, more tersely\r\nauto p = std::make_shared&lt;Sample&gt;();\r\nconsume({ p, &amp;p-&gt;value1 });\r\n<\/pre>\n<p>In the above example, we have a class <code>Sample<\/code> with two members. We create a <code>shared_ptr<\/code> to that class and save it in <code>p<\/code>. But say there&#8217;s another function that wants a <code>shared_ptr&lt;int&gt;<\/code>. No problem, we can convert the <code>std::shared_ptr&lt;Sample&gt;<\/code> into a <code>std::shared_ptr&lt;int&gt;<\/code> by reusing the control block (first parameter <code>p<\/code>) and substituting a new stored pointer (second parameter <code>&amp;p-&gt;value1<\/code>). The <code>consume<\/code> function can use the <code>shared_ptr&lt;int&gt;<\/code> to access the <code>value1<\/code> member, and the control block of that <code>shared_ptr&lt;int&gt;<\/code> prevents the <code>Sample<\/code> from being destroyed, which in turn prevents the <code>value1<\/code> from being destroyed.<\/p>\n<p>The general principle is that the lifetime of the stored pointer should be contained with the lifetime of the managed object. It can be a direct containment relationship, like we did with <code>value1<\/code>, or it could be a more complex chain of lifetime dependencies.<\/p>\n<pre>struct Other\r\n{\r\n    int value;\r\n};\r\n\r\nstruct Sample2\r\n{\r\n    const std::unique_ptr&lt;Other&gt; m_other =\r\n        std::make_unique&lt;Other&gt;();\r\n};\r\n\r\nauto p = std::make_shared&lt;Sample2&gt;();\r\nconsume({ p, &amp;p-&gt;m_other-&gt;value });\r\n<\/pre>\n<p>In this second example, the stored pointer of the <code>shared_ptr&lt;int&gt;<\/code> we pass to the <code>consume()<\/code> function points to the <code>value<\/code> member inside the <code>Other<\/code> object to which the <code>Sample2<\/code> holds a unique pointer. The control block in that <code>shared_ptr&lt;int&gt;<\/code> controls the lifetime of the <code>Sample2<\/code> object, which is acceptable because as long as the <code>Sample2<\/code> object remains alive, the <code>value<\/code> inside the <code>Other<\/code> will be alive.<\/p>\n<p>Now, the compiler doesn&#8217;t check that you have a positive chain of lifetime control from the managed object to the stored pointer. You could do something silly like<\/p>\n<pre>struct Sample3\r\n{\r\n    std::unique_ptr&lt;Other&gt; m_other =\r\n        std::make_unique&lt;Other&gt;();\r\n};\r\n\r\nauto p = std::make_shared&lt;Sample3&gt;();\r\nconsume({ p, &amp;p-&gt;m_other-&gt;value });\r\np-&gt;m_other = nullptr; \/\/ oops, chain is broken\r\n<\/pre>\n<p>and the <code>shared_ptr&lt;int&gt;<\/code> will think it&#8217;s keeping the <code>value<\/code> alive, even though you broke the link from the <code>Sample3<\/code> to the <code>Other<\/code>.<\/p>\n<p>Or you can do something even sillier like<\/p>\n<pre>int unrelated;\r\nconsume({ p, &amp;unrelated });\r\n<\/pre>\n<p>and the <code>shared_ptr&lt;int&gt;<\/code> will access <code>unrelated<\/code> even though its lifetime is unrelated to the <code>Sample2<\/code>. If <code>unrelated<\/code> is destroyed, the <code>shared_ptr&lt;int&gt;<\/code> will have a dangling stored pointer.<\/p>\n<p>These <code>shared_ptr<\/code> objects in which the managed object is different from the pointed-to object are commonly known as <i>aliasing<\/i> shared pointers.<\/p>\n<p>Okay, so I showed one way of creating an aliasing shared pointer, namely by constructing a <code>shared_ptr<\/code> from an existing <code>shared_ptr<\/code> (which shares the managed object) and providing a different stored pointer. If the new stored pointer points to a base class of the original, then the <code>shared_ptr<\/code> has a conversion operator that creates an aliasing shared pointer to the base-class subobject.<\/p>\n<pre>struct Base\r\n{\r\n};\r\n\r\nstruct Derived : Base\r\n{\r\n};\r\n\r\nstd::shared_ptr&lt;Derived&gt; p = std::make_shared&lt;Derived&gt;();\r\nstd::shared_ptr&lt;Base&gt; b = p; \/\/ auto-conversion\r\n\/\/ equivalent to\r\nstd::shared_ptr&lt;Base&gt; b(p, p.get());\r\n<\/pre>\n<p>If you want to do the reverse conversion (from <code>Base<\/code> to <code>Derived<\/code>), you can write it out explicitly:<\/p>\n<pre>std::shared_ptr&lt;Derived&gt; b(p, static_cast&lt;Derived*&gt;(p.get()));\r\n<\/pre>\n<p>Of course, this requires that the stored <code>Base<\/code> pointer really is a pointer to the <code>Base<\/code> part of a larger <code>Derived<\/code> object.<\/p>\n<p>The C++ language comes with some helper functions that construct a <code>shared_ptr<\/code> by casting the stored pointer of another <code>shared_ptr<\/code>.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Helper<\/th>\n<th>Equivalent to <code>std::shared_ptr&lt;T&gt;{...}<\/code><\/th>\n<\/tr>\n<tr>\n<td><code>std::static_pointer_cast&lt;T&gt;(p)<\/code><\/td>\n<td><code>{ p, static_cast&lt;T*&gt;(p.get()) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>std::const_pointer_cast&lt;T&gt;(p)<\/code><\/td>\n<td><code>{ p, const_cast&lt;T*&gt;(p.get()) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>std::reinterpret_pointer_cast&lt;T&gt;(p)<\/code><\/td>\n<td><code>{ p, reinterpret_cast&lt;T*&gt;(p.get()) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>std::dynamic_pointer_cast&lt;T&gt;(p)<\/code><\/td>\n<td><code><span style=\"text-decoration: line-through;\">{ p, dynamic_cast&lt;T*&gt;(p.get()) }<\/span><\/code><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Everything looks great until we get to <code>dynamic_<wbr \/>pointer_<wbr \/>cast<\/code>, which is <i>not<\/i> equivalent to a one-liner that uses <code>dynamic_<wbr \/>cast<\/code>!<\/p>\n<p>The reason is that, unlike the other casts, <code>dynamic_cast<\/code> can change a non-null pointer to a null pointer, which happens if the runtime type does not match. In that case, the <code>dynamic_<wbr \/>pointer_<wbr \/>case<\/code> returns an empty shared pointer (rather than a shared pointer with a control block and no stored pointer), because there is nothing whose lifetime needs to be extended.<\/p>\n<p>Now we can finish that table:<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Helper<\/th>\n<th>Equivalent to <code>std::shared_ptr&lt;T&gt;{...}<\/code><\/th>\n<\/tr>\n<tr>\n<td><code>std::static_pointer_cast&lt;T&gt;(p)<\/code><\/td>\n<td><code>{ p, static_cast&lt;T*&gt;(p.get()) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>std::const_pointer_cast&lt;T&gt;(p)<\/code><\/td>\n<td><code>{ p, const_cast&lt;T*&gt;(p.get()) }<\/code><\/td>\n<\/tr>\n<tr>\n<td><code>std::reinterpret_pointer_cast&lt;T&gt;(p)<\/code><\/td>\n<td><code>{ p, reinterpret_cast&lt;T*&gt;(p.get()) }<\/code><\/td>\n<\/tr>\n<tr>\n<td rowspan=\"2\"><code>std::dynamic_pointer_cast&lt;T&gt;(p)<\/code><\/td>\n<td><code>{ p, dynamic_cast&lt;T*&gt;(p.get()) }<\/code><br \/>\n(if cast succeeds)<\/td>\n<\/tr>\n<tr>\n<td><code>{}<\/code> (if cast fails)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>This wrinkle about control blocks for null pointers does call out that two boxes in the shared pointer diagram are technically legal though strange.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse; text-align: center;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>\u00a0<\/th>\n<th>Null control block<\/th>\n<th>Non-null control block<\/th>\n<\/tr>\n<tr>\n<th>Null stored pointer<\/th>\n<td>Empty<\/td>\n<td>Phantom (?)<\/td>\n<\/tr>\n<tr>\n<th>Non-null stored pointer<\/th>\n<td>Indulgent (?)<\/td>\n<td>Full<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>So far, we&#8217;ve been dealing with empty shared pointers (that manage no object and have no stored pointer) and full shared pointers (that manage an object and have a stored pointer). But there are two other boxes, which I&#8217;ve named &#8220;Phantom&#8221; and &#8220;Indulgent&#8221;. We&#8217;ll look at those two weird guys next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Changing the pointer while controlling the same object.<\/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-108611","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Changing the pointer while controlling the same object.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108611","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=108611"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/108611\/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=108611"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=108611"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=108611"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}