{"id":111497,"date":"2025-08-25T07:00:00","date_gmt":"2025-08-25T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111497"},"modified":"2025-08-16T17:45:26","modified_gmt":"2025-08-17T00:45:26","slug":"20250825-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250825-00\/?p=111497","title":{"rendered":"Thoughts on creating a tracking pointer class, part 11: Repairing assignment"},"content":{"rendered":"<p>Last time, we <a title=\"Thoughts on creating a tracking pointer class, part 10: Proper conversion\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250822-00\/?p=111494\"> made sure that tracking pointers to const objects couldn&#8217;t be converted into tracking pointers to non-const objects<\/a>, but I noted that fixing this introduced a new problem.<\/p>\n<p>We fixed the problem by introducing two new constructors that allow construction of either a <code>tracking_ptr&lt;const T&gt;<\/code> or <code>tracking_ptr&lt;T&gt;<\/code> from <code>tracking_ptr&lt;T&gt;<\/code>. If the destination is a <code>tracking_ptr&lt;T&gt;<\/code>, then the copy or move construction from <code>tracking_ptr&lt;T&gt;<\/code> merely overrides the copy or move construction inherited from the base class, so there is no redeclaration conflict.<\/p>\n<p>The problem is that in the case of <code>tracking_ptr&lt;T&gt;<\/code>, the new constructors are copy and move constructors since they construct from another instance of the same type. And if you declare a move constructor, then the copy and move assignment operators are implicitly declared as deleted.<\/p>\n<p>So we need to bring them back.<\/p>\n<pre>template&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 MP = tracking_ptr&lt;std::remove_cv_t&lt;T&gt;&gt;;\r\n\r\npublic:\r\n    T* get() const { return this-&gt;tracked; }\r\n\r\n    using base::base;\r\n    tracking_ptr(MP const&amp; other) : base(other) {}\r\n    tracking_ptr(MP&amp;&amp; other) : base(std::move(other)) {}\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">tracking_ptr&amp; operator=(tracking_ptr const&amp;) = default;<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">tracking_ptr&amp; operator=(tracking_ptr&amp;&amp;) = default;     <\/span>\r\n};\r\n<\/pre>\n<p>But now we have the reverse problem: If you declare a copy or move assignment, then the copy and move <i>constructors<\/i> are implicitly declared as deleted.<\/p>\n<p>So we have to bring those back too:<\/p>\n<pre>template&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 MP = tracking_ptr&lt;std::remove_cv_t&lt;T&gt;&gt;;\r\n\r\npublic:\r\n    T* get() const { return this-&gt;tracked; }\r\n\r\n    using base::base;\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">tracking_ptr(tracking_ptr const&amp; other) : base(other) {}      <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">tracking_ptr(tracking_ptr&amp;&amp; other) : base(std::move(other)) {}<\/span>\r\n    tracking_ptr(MP const&amp; other) : base(other) {}\r\n    tracking_ptr(MP&amp;&amp; other) : base(std::move(other)) {}\r\n\r\n    tracking_ptr&amp; operator=(tracking_ptr const&amp;) = default;\r\n    tracking_ptr&amp; operator=(tracking_ptr&amp;&amp;) = default;\r\n};\r\n<\/pre>\n<p>And now we have the double-definition problem we saw last time: In the case of <code>tracking_ptr&lt;T&gt;<\/code> where <code>T<\/code> is non-const, we have two declarations for the same copy constructor (and two for the same move constructor), which is not allowed.<\/p>\n<p>There&#8217;s another problem: In the case of assigning a <code>tracking_ptr&lt;const T&gt;<\/code> to a <code>tracking_ptr&lt;T&gt;<\/code>, we actually perform it in two steps: First we convert the <code>tracking_ptr&lt;const T&gt;<\/code> to a <code>tracking_ptr&lt;T&gt;<\/code>, and then we assign the <code>tracking_ptr&lt;T&gt;<\/code> to its destination. This creates a temporary <code>tracking_ptr&lt;T&gt;<\/code> that gets linked into the chain, and then unlinked. Can we avoid that inefficiency and just assign it directly?<\/p>\n<p>It turns out the same trick works for both problems.<\/p>\n<pre>template&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    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">using Source = std::conditional_t&lt;std::is_const_v&lt;T&gt;,<\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">    base, tracking_ptr&lt;std::remove_cv_t&lt;T&gt;&gt;&gt;;        <\/span>\r\n\r\npublic:\r\n    T* get() const { return this-&gt;tracked; }\r\n\r\n    using base::base;\r\n    tracking_ptr(<span style=\"border: solid 1px currentcolor;\">Source<\/span> const&amp; other) : base(other) {}\r\n    tracking_ptr(<span style=\"border: solid 1px currentcolor;\">Source<\/span>&amp;&amp; other) : base(std::move(other)) {}\r\n\r\n    <span style=\"border: solid 1px currentcolor; border-bottom: none;\">tracking_ptr&amp; operator=(Source const&amp; other) {   <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    static_cast&lt;base&amp;&gt;(*this) = other;           <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return *this;                                <\/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&amp; operator=(Source&amp;&amp; other) {        <\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    static_cast&lt;base&amp;&gt;(*this) = std::move(other);<\/span>\r\n    <span style=\"border: 1px currentcolor; border-style: none solid;\">    return *this;                                <\/span>\r\n    <span style=\"border: solid 1px currentcolor; border-top: none;\">}                                                <\/span>\r\n};\r\n<\/pre>\n<p>If creating a <code>tracking_ptr&lt;const T&gt;<\/code>, then we accept assignment or construction from either <code>tracking_ptr&lt;T&gt;<\/code> or <code>tracking_ptr&lt;const T&gt;<\/code>. But if creating a <code>tracking_ptr&lt;T&gt;<\/code> where <code>T<\/code> is non-const, then we accept assignment or construction only from another <code>tracking_ptr&lt;T&gt;<\/code>. This is expressed in the definition of <code>Source<\/code>, which says that tracking pointers to const things can accept the base type, which means that it will accept any type of tracking pointer to that thing (either to a const or non-const thing). But if it&#8217;s a tracking pointer to a non-const thing, then it accepts only tracking pointers to the same non-const thing.<\/p>\n<p>We also have to write out the copy and move assignment operators. We could use <code>= default<\/code> in the case where the <code>Source<\/code> is equal to <code>tracking_ptr&lt;T&gt;<\/code>, but if dealing with a tracking pointer to a const thing, the <code>Source<\/code> is the <code>base<\/code>, and the compiler doesn&#8217;t know how to default-assign that. So we just write it out explicitly, which works for both cases.<\/p>\n<p>So are we done? I guess.<\/p>\n<p>But wait.<\/p>\n<p>Recall that the complexity of moving a trackable object is linear in the number of tracking pointers because we have to update all the tracking pointers to point to the new location of the moved object. But we can get the cost down to <var>O<\/var>(1) if we are willing to make some concessions. We&#8217;ll look at this alternate design next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Restoring things that were automatically deleted.<\/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-111497","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Restoring things that were automatically deleted.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111497","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=111497"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111497\/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=111497"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111497"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111497"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}