{"id":111506,"date":"2025-08-26T07:00:00","date_gmt":"2025-08-26T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111506"},"modified":"2025-08-16T18:14:06","modified_gmt":"2025-08-17T01:14:06","slug":"20250826-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250826-00\/?p=111506","title":{"rendered":"Thoughts on creating a tracking pointer class, part 12: A shared tracking pointer"},"content":{"rendered":"<p>The tracking pointer designs we&#8217;ve been using so far have had <var>O<\/var>(<var>n<\/var>) complexity on move, where <var>n<\/var> is the number of outstanding tracking pointers. But we can reduce this to <var>O<\/var>(1) by the classic technique of introducing another level of indirection.<\/p>\n<p>What we can do is give every trackable object a single <code>shared_ptr&lt;T*&gt;<\/code> (which we call the &#8220;tracker&#8221;), which is shared with all tracking pointers. That way, when the object is moved, we can update that single <code>shared_ptr&lt;T*&gt;<\/code>, and that updates the pointer for all the tracking pointers.<\/p>\n<pre>template&lt;typename T&gt; struct trackable_object;\r\n\r\n<span style=\"border: solid 1px currentcolor;\">\/\/ <span style=\"text-decoration: line-through;\">struct tracking_node { ... };<\/span><\/span>\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">template&lt;typename T&gt;                                            <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">struct tracking_ptr_base                                        <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">{                                                               <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">    tracking_ptr_base() noexcept = default;                     <\/span>\r\n                                                                \r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">private:                                                        <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">    friend struct trackable_object&lt;T&gt;;                          <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">    tracking_ptr_base(std::shared_ptr&lt;T*&gt; const&amp; ptr) noexcept :<\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">        m_ptr(ptr) { }                                          <\/span>\r\n                                                                \r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">protected:                                                      <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">    std::shared_ptr&lt;T*&gt; m_ptr;                                  <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">};                                                              <\/span>\r\n\r\ntemplate&lt;typename T&gt;\r\nstruct tracking_ptr : tracking_ptr_base&lt;std::remove_cv_t&lt;T&gt;&gt;\r\n{\r\nprivate:\r\n    using base = tracking_ptr_base&lt;std::remove_cv_t&lt;T&gt;&gt;;\r\n    using Source = std::conditional_t&lt;std::is_const_v&lt;T&gt;,\r\n        base, tracking_ptr&lt;std::remove_cv_t&lt;T&gt;&gt;&gt;;\r\n\r\npublic:\r\n    <span style=\"border: solid 1px currentcolor;\">T* get() const { return this-&gt;m_ptr ? *this-&gt;m_ptr : nullptr; }<\/span>\r\n\r\n    using base::base;\r\n    tracking_ptr(Source const&amp; other) : base(other) {}\r\n    tracking_ptr(Source&amp;&amp; other) : base(std::move(other)) {}\r\n\r\n    tracking_ptr&amp; operator=(Source const&amp; other) {\r\n        static_cast&lt;base&amp;&gt;(*this) = other;\r\n        return *this;\r\n    }\r\n    tracking_ptr&amp; operator=(Source&amp;&amp; other) {\r\n        static_cast&lt;base&amp;&gt;(*this) = std::move(other);\r\n        return *this;\r\n    }\r\n};\r\n<\/pre>\n<p>The tracking pointer (via the tracking pointer base) holds a copy of the tracker shared pointer. The small catch here is that the tracker <code>m_ptr<\/code> might be null if the tracking pointer was default-constructed or has been moved-from, so the <code>get<\/code> method needs to check for a non-null pointer before dereferencing 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;\">\/* <span style=\"text-decoration: line-through;\">noexcept<\/span> *\/<\/span> = default;\r\n\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;\">\/* <span style=\"text-decoration: line-through;\">noexcept<\/span> *\/<\/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;\">\/* <span style=\"text-decoration: line-through;\">noexcept<\/span> *\/<\/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;\">\/* <span style=\"text-decoration: line-through;\">noexcept<\/span> *\/<\/span> {\r\n        set_target(nullptr);\r\n        m_tracker = other.transfer_out();\r\n        set_target(owner());\r\n        return *this;\r\n    }\r\n\r\n    tracking_ptr&lt;T&gt; track() noexcept {\r\n        return { m_tracker };\r\n    }\r\n\r\n    tracking_ptr&lt;const T&gt; track() const noexcept {\r\n        return { m_tracker };\r\n    }\r\n\r\n    tracking_ptr&lt;const T&gt; ctrack() const noexcept {\r\n        return { m_tracker };\r\n    }\r\n\r\nprivate:\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    std::shared_ptr&lt;T*&gt; new_tracker()\r\n    {\r\n        return std::make_shared&lt;T*&gt;(owner());\r\n    }\r\n\r\n    std::shared_ptr&lt;T*&gt; transfer_out()\r\n    {\r\n        return std::exchange(m_tracker, new_tracker());\r\n    }\r\n\r\n    void set_target(T* p) noexcept\r\n    {\r\n        *m_tracker = p;\r\n    }\r\n\r\n    std::shared_ptr&lt;T*&gt; m_tracker = new_tracker();\r\n};\r\n<\/pre>\n<p>The trackable object starts out with a new tracker that points to the newly-constructed object. On destruction, the trackable object nulls out the backpointer in the tracker, which causes any existing tracking pointers to expire.<\/p>\n<p>As with our other trackable object implementations, copying a trackable object has no effect on the tracker, and moving it transfers the tracker to the new object, abandoning any existing tracker. When we move the tracker to the new object, we need to leave a fresh (not-yet-shared-with-anybody) tracker behind so that the moved-from object is still trackable if anybody asks.<\/p>\n<p>The helper method <code>new_tracker()<\/code> makes a fresh tracker that tracks the current object. The helper method <code>transfer_out()<\/code> relinquishes the current tracker (presumably so it can be given to the moved-to object) and sets up a fresh new tracker.<\/p>\n<p>Although this improves the complexity of moving a trackable object to constant time, the requirement that <code>m_tracker<\/code> be non-empty means that the constructors and the move-assignment operators are now throwing, because <code>new_tracker()<\/code> could fail.<\/p>\n<p>So now we have to look at whether the inability to create a new tracker could cause us to violate our invariants.<\/p>\n<p>An exception in the constructors doesn&#8217;t affect our invariants because we simply decided not to exist at all.<\/p>\n<p>An exception in the move assignment operator is more troublesome. If <code>transfer_out()<\/code> fails, we have already disconnected the trackers, so a failure to transfer out causes existing tracking pointers to the destination to expire. This violates the strong exception guarantee, which says that if an exception occurs, the object remains unchanged.<\/p>\n<p>We&#8217;ll fix this next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sharing a single tracking pointer among all instances.<\/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-111506","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Sharing a single tracking pointer among all instances.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111506","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=111506"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111506\/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=111506"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111506"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111506"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}