{"id":111524,"date":"2025-08-28T07:00:00","date_gmt":"2025-08-28T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111524"},"modified":"2025-08-16T18:18:58","modified_gmt":"2025-08-17T01:18:58","slug":"20250828-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250828-00\/?p=111524","title":{"rendered":"Thoughts on creating a tracking pointer class, part 14: Nonthrowing moves with the shared tracking pointer"},"content":{"rendered":"<p>So far, we&#8217;ve been working on <a title=\"Thoughts on creating a tracking pointer class, part 12: A shared tracking pointer\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250827-00\/?p=111518\"> an alternate design for tracking pointers<\/a>, but we found that it had the unfortunate property of having potentially-throwing constructors and move assignment operations.<\/p>\n<p>We can make these operations non-throwing by removing the need for a trackable object always to have a ready-made tracker. Instead, we can create a tracker on demand the first time somebody asks to track it. The exception doesn&#8217;t go away, but it defers it to the time a tracking pointer is created. This is arguably a good thing because it makes tracking pointers &#8220;pay for play&#8221;: You don&#8217;t allocate a tracker until somebody actually needs it.<\/p>\n<pre>template&lt;typename T&gt;\r\nstruct trackable_object\r\n{\r\n    trackable_object() <span style=\"border: solid 1px currentcolor;\">noexcept<\/span> = default;\r\n    ~trackable_object()\r\n    {\r\n        set_target(nullptr);\r\n    }\r\n\r\n    \/\/ Copy constructor: Separate trackable object\r\n    trackable_object(const trackable_object&amp;) <span style=\"border: solid 1px currentcolor;\">noexcept<\/span> :\r\n        trackable_object()\r\n    { }\r\n\r\n    \/\/ Move constructor: Transfers tracker\r\n    trackable_object(trackable_object&amp;&amp; other) <span style=\"border: solid 1px currentcolor;\">noexcept<\/span> :\r\n        m_tracker(other.transfer_out()) {\r\n        set_target(owner());\r\n    }\r\n\r\n    \/\/ Copying has no effect on tracking pointers\r\n    trackable_object&amp;\r\n        operator=(trackable_object const&amp;) noexcept\r\n    {\r\n        return *this;\r\n    }\r\n\r\n    \/\/ Moving abandons current tracking pointers and\r\n    \/\/ transfers tracking pointers from the source\r\n    trackable_object&amp;\r\n        operator=(trackable_object&amp;&amp; other) <span style=\"border: solid 1px currentcolor;\">noexcept<\/span> {\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">set_target(nullptr);             <\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">m_tracker = other.transfer_out();<\/span>\r\n        set_target(owner());\r\n        return *this;\r\n    }\r\n\r\n    tracking_ptr&lt;T&gt; track() <span style=\"border: solid 1px currentcolor;\">\/* <span style=\"text-decoration: line-through;\">noexcept<\/span> *\/<\/span> {\r\n        <span style=\"border: solid 1px currentcolor;\">ensure_tracker();<\/span>\r\n        return { m_tracker };\r\n    }\r\n\r\n    tracking_ptr&lt;const T&gt; track() const <span style=\"border: solid 1px currentcolor;\">\/* <span style=\"text-decoration: line-through;\">noexcept<\/span> *\/<\/span> {\r\n        <span style=\"border: solid 1px currentcolor;\">ensure_tracker();<\/span>\r\n        return { m_tracker };\r\n    }\r\n\r\n    tracking_ptr&lt;const T&gt; ctrack() const <span style=\"border: solid 1px currentcolor;\">\/* <span style=\"text-decoration: line-through;\">noexcept<\/span> *\/<\/span> {\r\n        <span style=\"border: solid 1px currentcolor;\">ensure_tracker();<\/span>\r\n        return { m_tracker };\r\n    }\r\n\r\nprivate:\r\n    std::shared_ptr&lt;T*&gt; <span style=\"border: solid 1px currentcolor;\">mutable<\/span> m_tracker;\r\n\r\n    T* owner() const noexcept {\r\n        return const_cast&lt;T*&gt;(static_cast&lt;const T*&gt;(this));\r\n    }\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">void ensure_tracker() const                       <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                 <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    if (!m_tracker)                               <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    {                                             <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">        m_tracker = std::make_shared&lt;T*&gt;(owner());<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    }                                             <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                 <\/span>\r\n\r\n    std::shared_ptr&lt;T*&gt; transfer_out()\r\n    {\r\n        <span style=\"border: solid 1px currentcolor;\">return std::move(m_tracker);<\/span>\r\n    }\r\n\r\n    void set_target(T* p)\r\n    {\r\n        <span style=\"border: solid 1px currentcolor;\">if (m_tracker)<\/span>\r\n        {\r\n            *m_tracker = p;\r\n        }\r\n    }\r\n};\r\n<\/pre>\n<p>We make the <code>m_tracker<\/code> mutable because <code>ensure_tracker()<\/code> might be asked to create a tracker on demand from a const reference.<\/p>\n<p>Creating the tracker on demand removes the exception from the default constructor, the move and copy constructors, and the move and copy assignments. The potentially-throwing behavior moves to the <code>track()<\/code> and <code>ctrack()<\/code> methods, but that can be sort of justified on the principle of &#8220;pay for play&#8221;.<\/p>\n<p>Now, if you look more closely at what we have, you may notice that the <code>shared_ptr<\/code> is overkill. We don&#8217;t use weak pointers, and all of our operations are single-threaded, so the atomic memory barriers inside the <code>shared_ptr<\/code> operations are not necessary. We&#8217;ll create a &#8220;limited-use single-threaded&#8221; version of the <code>shared_ptr<\/code> next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Moving the exception somewhere else.<\/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-111524","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Moving the exception somewhere else.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111524","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=111524"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111524\/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=111524"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111524"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111524"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}