{"id":111518,"date":"2025-08-27T07:00:00","date_gmt":"2025-08-27T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111518"},"modified":"2025-08-16T18:15:50","modified_gmt":"2025-08-17T01:15:50","slug":"20250827-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250827-00\/?p=111518","title":{"rendered":"Thoughts on creating a tracking pointer class, part 13: Restoring the strong exception guarantee"},"content":{"rendered":"<p>Last time, we <a title=\"Thoughts on creating a tracking pointer class, part 12: A shared tracking pointer\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250826-00\/?p=111506\"> created a tracking pointer class based on <code>std::<wbr \/>shared_ptr<\/code><\/a>. We found a problem with the move-assignment operator: It didn&#8217;t satisfy the strong exception guarantee:<\/p>\n<pre>    trackable_object&amp;\r\n        operator=(trackable_object&amp;&amp; other) {\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<\/pre>\n<p>If an exception occurs after we set the target to <code>nullptr<\/code> we exit with all the tracking pointers expired, which violates the rule that an exception leaves the object state unchanged.<\/p>\n<p>To fix this, we cannot make any irreversible changes until we have passed the point where the last exception could be raised. The exception occurs in the call to <code>new_tracker()<\/code> inside <code>transfer_out()<\/code>. When that happens, the exchange into <code>other.<wbr \/>m_tracker<\/code> does not occur, so <code>m_tracker<\/code> is safely unchanged. So we just need to delay expiring the old tracking pointers until after we have successfully transferred out.<\/p>\n<pre>    trackable_object&amp;\r\n        operator=(trackable_object&amp;&amp; other) {\r\n        <span style=\"border: solid 1px currentcolor;\">auto inbound = other.transfer_out();<\/span>\r\n        set_target(nullptr);\r\n        <span style=\"border: solid 1px currentcolor;\">m_tracker = inbound;<\/span>\r\n        set_target(owner());\r\n        return *this;\r\n    }\r\n<\/pre>\n<p>We can code-golf this by using <code>std::exchange<\/code> to replace the <code>m_tracker<\/code> while saving the old value, and then updating the target of that tracker manually.<\/p>\n<pre>    trackable_object&amp;\r\n        operator=(trackable_object&amp;&amp; other) {\r\n        <span style=\"border: solid 1px currentcolor; border-bottom: none;\">auto old = std::exchange(m_tracker, other.transfer_out());<\/span>\r\n        <span style=\"border: solid 1px currentcolor; border-top: none;\">*old = nullptr;                                           <\/span>\r\n        set_target(owner());\r\n        return *this;\r\n    }\r\n<\/pre>\n<p>And another iteration of code golfing to inline the result:<\/p>\n<pre>    trackable_object&amp;\r\n        operator=(trackable_object&amp;&amp; other) {\r\n        <span style=\"border: solid 1px currentcolor;\">*std::exchange(m_tracker, other.transfer_out()) = nullptr;<\/span>\r\n        set_target(owner());\r\n        return *this;\r\n    }\r\n<\/pre>\n<p>We noted last time that the constructors are also potentially-throwing. Many C++ algorithms and classes are significantly more efficient if they know that move operations cannot throw, so making the move constructor and move assignment operator potentially-throwing could end up begin quite expensive. And you probably expect operations like <code>vector::insert<\/code> and <code>std::sort<\/code> to move elements rather than copy them. Furthermore, many collection operations (such as <code>vector::insert<\/code> and <code>vector::erase<\/code>) leave the vector in an &#8220;unspecified&#8221; state if a move assignment throws an exception.\u00b9<\/p>\n<p>With the throwing move assignment operator, we have to be careful to consider the state of the trackable object if <code>transfer_out()<\/code> fails. In that case, we have already disconnected the trackers, so a failure to copy nevertheless breaks tracking pointers, which violates the strong exception guarantee.<\/p>\n<p>To fix that, we don&#8217;t abandon the old tracking pointers until we are sure we can get new ones.<\/p>\n<p>Next time, we&#8217;ll make the constructors and move-assignment operations non-throwing, though it comes at a cost.<\/p>\n<p>\u00b9 Another side effect is that it prevents trackable objects from being nothrow-swappable, since swapping is based on move operations. We could add a custom <code>swap<\/code> method and a custom overload of <code>std::swap<\/code>, but that also creates the onus on the derived class to provide the same customizations on itself so that it can forward the methods into <code>trackable_object<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Don&#8217;t commit to anything until you know you can finish the job.<\/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-111518","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Don&#8217;t commit to anything until you know you can finish the job.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111518","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=111518"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111518\/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=111518"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111518"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111518"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}