{"id":107248,"date":"2022-10-05T07:00:00","date_gmt":"2022-10-05T14:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=107248"},"modified":"2022-10-05T15:35:00","modified_gmt":"2022-10-05T22:35:00","slug":"20221005-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20221005-00\/?p=107248","title":{"rendered":"The gotcha of the C++ temporaries that don&#8217;t destruct as eagerly as you thought"},"content":{"rendered":"<p>Forgetting to take a lock when updating variables is a common mistake. One way to make the mistake harder to make is to <i>force<\/i> the access to occur through some mechanism that proves that you possess the lock. Maybe something like this:<\/p>\n<pre>template&lt;typename&gt; struct LockableData;\r\n\r\nnamespace std\r\n{\r\n    template&lt;typename Data&gt;\r\n    struct default_delete&lt;LockableData&lt;Data&gt;&gt;\r\n    {\r\n        void operator()(LockableData&lt;Data&gt;* p)\r\n        const noexcept { p-&gt;m.unlock(); }\r\n    };\r\n}\r\n\r\ntemplate&lt;typename Lockable&gt;\r\nstruct [[nodiscard]] LockedData\r\n{\r\n    LockedData(Lockable* l = nullptr) : l(l)\r\n    { if (l) l-&gt;m.lock(); }\r\n\r\n    auto operator-&gt;() const noexcept\r\n    { return std::addressof(l-&gt;data); }\r\n\r\nprivate:\r\n    std::unique_ptr&lt;Lockable&gt; l;\r\n};\r\n\r\ntemplate&lt;typename Data&gt;\r\nstruct LockableData\r\n{\r\n    LockedData&lt;LockableData&gt; Lock() { return this; }\r\n\r\nprivate:\r\n    friend struct LockedData&lt;LockableData&gt;;\r\n    friend struct std::default_delete&lt;LockableData&gt;;\r\n\r\n    std::mutex m;\r\n    Data data;\r\n};\r\n<\/pre>\n<p>The idea here is that you declare some structure that holds the data you want to be protected by a common mutex. You can then wrap that data inside a <code>LockableData<\/code>. To access the data, you call <code>Lock()<\/code> to acquire the mutex and receive a <code>LockedData<\/code> object. You then access the structure through the <code>LockedData<\/code> object, and when the <code>LockedData<\/code> object destructs, it releases the mutex.<\/p>\n<p>Using a <code>std::unique_ptr<\/code> with a custom deleter allows the <code>LockedData<\/code> object to be movable with the natural semantics. And marking the <code>LockedData<\/code> as <code>[[nodiscard]]<\/code> makes sure that you save the return value of <code>Lock()<\/code>; otherwise, it destructs immediately, and your lock accomplished nothing.<\/p>\n<p>Here&#8217;s an example usage:<\/p>\n<pre>struct WidgetInfo\r\n{\r\n    std::string name;\r\n    int times_toggled = 0;\r\n};\r\n\r\nclass Widget\r\n{\r\n    LockableData&lt;WidgetInfo&gt; info;\r\n\r\npublic:\r\n    void SetName(std::string name)\r\n    {\r\n        auto lock = info.Lock();\r\n        lock-&gt;name = name;\r\n        lock-&gt;times_toggled = 0;\r\n    }\r\n\r\n    std::string GetName()\r\n    {\r\n        auto lock = info.Lock();\r\n        return lock-&gt;name;\r\n    }\r\n\r\n    void Toggle()\r\n    {\r\n        { \/\/ scope the lock\r\n            auto lock = info.Lock();\r\n            lock-&gt;times_toggled++;\r\n        }\r\n        FlipSwitchesRandomly();\r\n    }\r\n};\r\n<\/pre>\n<p>One thing that&#8217;s slightly annoying here is that in many cases, we are locking around the access to a single member. One way to avoid having to create tiny scopes is to allow the <code>-&gt;<\/code> operator to be used directly from the <code>LockableData<\/code>, so that it does a lock-access-unlock.<\/p>\n<pre>template&lt;typename Data&gt;\r\nstruct LockableData\r\n{\r\n    LockedData&lt;LockableData&gt; Lock() { return this; }\r\n    <span style=\"color: #08f;\">auto operator-&gt;() { return Lock(); } \/\/ NEW!<\/span>\r\n\r\nprivate:\r\n    friend struct LockedData&lt;LockableData&gt;;\r\n    friend struct std::default_delete&lt;LockableData&gt;;\r\n\r\n    std::mutex m;\r\n    Data data;\r\n};\r\n\r\nclass Widget\r\n{\r\n    LockableData&lt;WidgetInfo&gt; info;\r\n\r\npublic:\r\n    void SetName(std::string name)\r\n    {\r\n        auto lock = info.Lock();\r\n        lock-&gt;name = name;\r\n        lock-&gt;times_toggled = 0;\r\n    }\r\n\r\n    std::string GetName()\r\n    {\r\n        <span style=\"color: #08f;\">return info-&gt;name; \/\/ lock-read-unlock<\/span>\r\n    }\r\n\r\n    void Toggle()\r\n    {\r\n        <span style=\"color: #08f;\">info-&gt;times_toggled++; \/\/ lock-modify-unlock<\/span>\r\n        FlipSwitchesRandomly();\r\n    }\r\n};\r\n<\/pre>\n<p>This convenience <code>-&gt;<\/code> operator makes single-member updates much easier, but it also comes with a catch:<\/p>\n<pre>    void Toggle()\r\n    {\r\n        <span style=\"color: #08f;\">info-&gt;times_toggled = std::max(info-&gt;times_toggled, 10);<\/span>\r\n        FlipSwitchesRandomly();\r\n    }\r\n<\/pre>\n<p>Do you see the problem here?<\/p>\n<p>I mean, yes, there&#8217;s a race condition here if two threads toggle at the same time, but that&#8217;s not the problem I&#8217;m referring to. That would &#8220;merely&#8221; result in incorrect accounting.<\/p>\n<p>The real problem is the double lock.<\/p>\n<p>The <code>-&gt;<\/code> operator returns a temporary <code>LockedData<\/code> object, and the rules for temporary objects in C++ are that they are destructed at the end of the <i>full expression<\/i>.<\/p>\n<p>Therefore, the evaluation of the revised code goes like this:<\/p>\n<pre>    \/\/ Evaluate right hand side\r\n    LockedData&lt;WidgetInfo&gt; lock1 = info.operator-&gt;();\r\n    int rhs = std::max(lock1-&gt;times_toggled, 10);\r\n\r\n    \/\/ Evaluate left hand side\r\n    LockedData&lt;WidgetInfo&gt; lock2 = info.operator-&gt;();\r\n\r\n    \/\/ Perform the assignment\r\n    lock2-&gt;times_toggled = rhs;\r\n\r\n    \/\/ Destruct temporaries in reverse order of construction\r\n    destruct lock2;\r\n    destruct rhs;\r\n    destruct lock1;\r\n<\/pre>\n<p>Do you see the problem yet?<\/p>\n<p>Since the locks are temporaries, their lifetime extends to the end of the full expression. <a title=\"Spotting problems with destructors for C++ temporaries\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20190429-00\/?p=102456\"> We&#8217;ve seen this problem before<\/a>.\u00b9 This means that the two <code>info-&gt;times_toggled<\/code> operations each create a separate <code>LockedData<\/code> object, and each one acquires the mutex.<\/p>\n<p>The result is that we acquire the mutex lock twice, which is not allowed. In practice, this results in a deadlock and forces you to <a href=\"https:\/\/twitter.com\/windowsinsider\/status\/1433615378362503177\"> issue a public apology<\/a>.<\/p>\n<p>The convenience <code>-&gt;<\/code> was a bit <i>too convenient<\/i>: If an object <code>o<\/code> is a smart pointer, people are accustomed to <code>o-&gt;Something<\/code> doing some work with <code>o<\/code> in order to produce the pointer that will be dereferenced in order to access the <code>Something<\/code>. What they are not accustomed to is the presence of work that occurs after the dereference to clean up.<\/p>\n<p>In other words, people tend to expect that it&#8217;s okay to call the <code>-&gt;<\/code> operator many times on the same object without consequence.<\/p>\n<p>So take away that dangerous operator. People can still get the one-liner convenience, though. They just have to lock explicitly:<\/p>\n<pre>    std::string GetName()\r\n    {\r\n        return info.Lock()-&gt;name;\r\n    }\r\n<\/pre>\n<p>Having to write out the word &#8220;Lock&#8221; also makes it easier to spot that you&#8217;re locking twice within the same expression.<\/p>\n<pre>    void Toggle()\r\n    {\r\n        \/\/ suspicious double-lock - more likely to be spotted in code review\r\n        info.Lock()-&gt;times_toggled = std::max(info.Lock()-&gt;times_toggled, 10);\r\n        FlipSwitchesRandomly();\r\n    }\r\n<\/pre>\n<p>\u00b9 It looks like C++23 has adopted the idea of the <code>out_ptr<\/code> temporary object which resets the pointer on destruction, in spite of the <a href=\"https:\/\/www.open-std.org\/jtc1\/sc22\/wg21\/docs\/papers\/2021\/p1132r8.html#design-footguns\"> footguns<\/a> that arise in certain usage patterns.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>You have to look for the end of the full expression.<\/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-107248","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>You have to look for the end of the full expression.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107248","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=107248"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/107248\/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=107248"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=107248"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=107248"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}