{"id":110783,"date":"2025-01-20T07:00:00","date_gmt":"2025-01-20T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=110783"},"modified":"2025-01-20T15:42:33","modified_gmt":"2025-01-20T23:42:33","slug":"20250120-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20250120-00\/?p=110783","title":{"rendered":"Reminder: When a C++ object fails to construct, the destructor does not run"},"content":{"rendered":"<p>In C++, if an object&#8217;s constructor fails (due to an exception), destructors are run for the object&#8217;s member variables and base classes, but not for the object itself. The principle at play is that you cannot destruct something that was never constructed in the first place.<\/p>\n<p>Consider <a href=\"https:\/\/github.com\/microsoft\/wil\/pull\/459\/files#diff-75fa14296d6e7e257caaffb45efcf7d7e895cde0c0526c2900026e6a12dbbb1dR3350\"> this pull request<\/a>:<\/p>\n<pre>com_timeout_t(DWORD timeoutInMilliseconds)\r\n    : m_threadId(GetCurrentThreadId())\r\n{\r\n    m_cancelEnablementResult = CoEnableCallCancellation(nullptr);\r\n    err_policy::HResult(m_cancelEnablementResult);\r\n    if (SUCCEEDED(m_cancelEnablementResult))\r\n    {\r\n        m_timer.reset(CreateThreadpoolTimer(\r\n            &amp;com_timeout_t::timer_callback, this, nullptr));\r\n        err_policy::LastErrorIfFalse(\r\n            static_cast&lt;bool&gt;(m_timer));\r\n        if (m_timer)\r\n        {\r\n            FILETIME ft = filetime::get_system_time();\r\n            ft = filetime::add(ft, filetime::\r\n                    convert_msec_to_100ns(timeoutInMilliseconds));\r\n            SetThreadpoolTimer(m_timer.get(), &amp;ft,\r\n                    timeoutInMilliseconds, 0);\r\n        }\r\n    }\r\n}\r\n\r\n~com_timeout_t()\r\n{\r\n    m_timer.reset();\r\n\r\n    if (SUCCEEDED(m_cancelEnablementResult))\r\n    {\r\n        CoDisableCallCancellation(nullptr);\r\n    }\r\n}\r\n\r\n\u27e6 member variables: \u27e7\r\n\r\nHRESULT m_cancelEnablementResult{};\r\nDWORD m_threadId{};\r\nbool m_timedOut{};\r\nwil::unique_threadpool_timer_nocancel m_timer;\r\n<\/pre>\n<p>The idea is that the constructor first calls <code>Co\u00adEnable\u00adCall\u00adCancellation<\/code> and reports the failure via <code>err_policy::<wbr \/>HResult()<\/code>. In the WIL library, the <code>err_policy<\/code> defines how the caller wants errors to be reported.<\/p>\n<table class=\"cp3\" style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td><tt>err_returncode_policy<\/tt><\/td>\n<td><tt>err_exception_policy<\/tt><\/td>\n<td><tt>err_failfast_policy<\/tt><\/td>\n<\/tr>\n<tr>\n<td><tt>HResult(hr)<\/tt> with failure<\/td>\n<td><tt>return hr;<\/tt><\/td>\n<td>Throw an exception<\/td>\n<td>Terminate the process<\/td>\n<\/tr>\n<tr>\n<td><tt>LastErrorIfFalse(false)<\/tt><\/td>\n<td><tt>return GetLastError();<\/tt><\/td>\n<td>Throw an exception<\/td>\n<td>Terminate the process<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>When writing code in WIL, you pass each result to the error policy so that it can report the error in the manner the caller requested.<\/p>\n<p>The code saves the result of <code>Co\u00adEnable\u00adCall\u00adCancellation<\/code> in <code>m_cancel\u00adEnablement\u00adResult<\/code> so that it knows whether it needs to call <code>Co\u00adDisable\u00adCall\u00adCancellation<\/code> at destruction to balance it out.<\/p>\n<p>The tricky case here is if <code>Co\u00adEnable\u00adCall\u00adCancellation<\/code> succeeds, but <code>Create\u00adThreadpool\u00adTimer<\/code> fails. If the error policy is <code>err_<wbr \/>exception_<wbr \/>policy<\/code>, this throws an exception out of the constructor, which <i>bypasses the destructor<\/i>. This means that the <code>Co\u00adDisable\u00adCall\u00adCancellation<\/code> never occurs, and we leak a call cancellation.<\/p>\n<p>If we want to clean up things that were done in the constructor, we have to ask a member variable or base class to clean it up for us.<\/p>\n<p>In this case, one solution is to <a href=\"https:\/\/github.com\/microsoft\/wil\/pull\/483\"> use the <code>wil::<wbr \/>unique_<wbr \/>call<\/code> to call a function at destruction<\/a>.<\/p>\n<pre><span style=\"border: solid 1px currentcolor; border-bottom: none;\">namespace details                              <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">{                                              <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">    inline void CoDisableCallCancellationNull()<\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">    {                                          <\/span>\r\n<span style=\"border: 1px currentcolor; border-style: none solid;\">        ::CoDisableCallCancellation(nullptr);  <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">    }                                          <\/span>\r\n} \/\/ namespace details\r\n\r\ncom_timeout_t(DWORD timeoutInMilliseconds)\r\n    : m_threadId(GetCurrentThreadId())\r\n{\r\n    const HRESULT cancelEnablementResult = CoEnableCallCancellation(nullptr);\r\n    err_policy::HResult(cancelEnablementResult);\r\n    if (SUCCEEDED(cancelEnablementResult))\r\n    {\r\n        <span style=\"border: solid 1px currentcolor;\">m_ensureDisable.activate();<\/span>\r\n        m_timer.reset(CreateThreadpoolTimer(\r\n            &amp;com_timeout_t::timer_callback, this, nullptr));\r\n        err_policy::LastErrorIfFalse(\r\n            static_cast&lt;bool&gt;(m_timer));\r\n        if (m_timer)\r\n        {\r\n            FILETIME ft = filetime::get_system_time();\r\n            ft = filetime::add(ft, filetime::\r\n                    convert_msec_to_100ns(timeoutInMilliseconds));\r\n            SetThreadpoolTimer(m_timer.get(), &amp;ft,\r\n                    timeoutInMilliseconds, 0);\r\n        }\r\n    }\r\n}\r\n\r\n\/\/ <span style=\"text-decoration: line-through;\">~com_timeout_t()<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">{<\/span>\r\n\/\/     <span style=\"text-decoration: line-through;\">m_timer.reset();<\/span>\r\n\/\/\r\n\/\/     <span style=\"text-decoration: line-through;\">if (SUCCEEDED(m_cancelEnablementResult))<\/span>\r\n\/\/     <span style=\"text-decoration: line-through;\">{<\/span>\r\n\/\/         <span style=\"text-decoration: line-through;\">CoDisableCallCancellation(nullptr);<\/span>\r\n\/\/     <span style=\"text-decoration: line-through;\">}<\/span>\r\n\/\/ <span style=\"text-decoration: line-through;\">}<\/span>\r\n\r\n\u27e6 member variables: \u27e7\r\n\r\n<span style=\"border: solid 1px currentcolor; border-bottom: none;\">wil::unique_call&lt;decltype(&amp;details::CoDisableCallCancellationNull),          <\/span>\r\n<span style=\"border: solid 1px currentcolor; border-top: none;\">            details::CoDisableCallCancellationNull, false&gt; m_ensureDisable{};<\/span>\r\nDWORD m_threadId{};\r\nbool m_timedOut{};\r\nwil::unique_threadpool_timer_nocancel m_timer;\r\n<\/pre>\n<p>The <code>wil::<wbr \/>unique_<wbr \/>call<\/code> calls the function described by its first two template parameters\u00b9 at destruction. The third template parameter (default <code>true<\/code>) specifies whether the object should be initially active. You can put the object into the active state by calling <code>activate()<\/code>, thereby enabling the call at destruction.<\/p>\n<p>We want to call <code>Co\u00adDisable\u00adCall\u00adCancellation<\/code> only if the <code>Co\u00adEnable\u00adCall\u00adCancellation<\/code> succeeded, so our <code>unique_<wbr \/>call<\/code> is initially inactive.<\/p>\n<p>Now, if there is an exception at construction, the member object <code>m_ensure\u00adDisable<\/code> will destruct and call <code>Co\u00adDisable\u00adCall\u00adCancellation<\/code> if necessary.<\/p>\n<p><b>Bonus chatter<\/b>: Note that for <code>err_<wbr \/>exception_<wbr \/>policy<\/code> and <code>err_<wbr \/>failfast<wbr \/>policy<\/code>, a failure to enable call cancellation prevents the constructor from running to completion, which means that the corresponding destructor <i>always<\/i> disables call cancellation. This means that the internal <code>bool<\/code> inside the <code>unique_<wbr \/>call<\/code> is always <code>true<\/code>, yet we consume data space for it and code space to check it.<\/p>\n<p>If we wanted to optimize further by avoiding the extra <code>bool<\/code> and the code to test it, we could use a different helper class depending on the error policy.<\/p>\n<pre>template&lt;typename err_policy&gt;\r\nstruct WithCallCancellation\r\n{\r\n    WithCallCancellation()\r\n    {\r\n        err_policy::HResult(CoEnableCallCancellation(nullptr));\r\n    }\r\n\r\n    WithCallCancellation(const WithCallCancellation&amp;) :\r\n        WithCallCancellation() { }\r\n\r\n    ~WithCallCancellation()\r\n    {\r\n        CoDisableCallCancellation(nullptr);\r\n    }\r\n\r\n    constexpr bool active() { return true; }\r\n};\r\n\r\ntemplate&lt;&gt;\r\nstruct WithCallCancellation&lt;err_returncode_policy&gt;\r\n{\r\n    WithCallCancellation() :\r\n        m_active(SUCCEEDED(\r\n                CoEnableCallCancellation(nullptr))) {}\r\n\r\n    WithCallCancellation(const WithCallCancellation&amp;) :\r\n        WithCallCancellation() { }\r\n\r\n    ~WithCallCancellation()\r\n    {\r\n        if (m_active) {\r\n            CoDisableCallCancellation(nullptr);\r\n        }\r\n    }\r\n\r\nprotected:\r\n    bool active() { return m_active; }\r\n    const bool m_active;\r\n}\r\n\r\n\r\ntemplate&lt;typename err_policy&gt;\r\ncom_timeout_t : <span style=\"border: solid 1px currentcolor;\">private WithCallCancellation&lt;err_policy&gt;<\/span>\r\n{\r\n    com_timeout_t(DWORD timeoutInMilliseconds)\r\n        : m_threadId(GetCurrentThreadId())\r\n    {\r\n        <span style=\"border: solid 1px currentcolor;\">if (this-&gt;WithCallCancellation::active())<\/span>\r\n        {\r\n            m_timer.reset(CreateThreadpoolTimer(\r\n                &amp;com_timeout_t::timer_callback, this, nullptr));\r\n            \u27e6 etc \u27e7\r\n        }\r\n    }\r\n}\r\n<\/pre>\n<p>The idea here is to take advantage of the empty base optimization (EBO) so that in the case where the error policy is not <code>err_<wbr \/>returncode_<wbr \/>policy<\/code>, we don&#8217;t waste space keeping track of something we know will always be true.\u00b2<\/p>\n<p>This solution is probably overkill for <code>com_timeout_t<\/code>, which is a class that is expected to have a short lifetime, and certainly not expected to have thousands of instances.<\/p>\n<p>\u00b9 WIL was written when C++11 was the new hotness. If it were written today, we would use <code>template&lt;auto&gt;<\/code> to collapse the two parameters into one.<\/p>\n<p><b>Bonus bonus chatter<\/b>: Another option would be to reorder the operations so that <code>Co\u00adEnable\u00adCall\u00adCancellation<\/code> is done last. That way, if the call fails, the <code>m_timer<\/code>&#8216;s destructor will clean up the timer. This is quicker, but it is also more fragile because somebody might add more initialization to the constructor later without realizing that the <code>Co\u00adEnable\u00adCall\u00adCancellation<\/code> must come last because we don&#8217;t have a member or base class to clean it up for us.<\/p>\n<p><b>Bonus bonus bonus chatter<\/b>: Removing the destructor from <code>com_<wbr \/>timeout_t<\/code> activates the default move constructor and move assignment operator, but we don&#8217;t want that because the timer callback has already captured the <code>this<\/code> pointer. As part of removing the constructor, the second PR also explicitly deletes the copy constructor and copy assignment operator (which in turn suppress the move constructor and move assignment operator).<\/p>\n<p>\u00b2 If you can assume C++20, then you can make it a member variable with <code>[[no_<wbr \/>unique_<wbr \/>address]]<\/code>. But note <a href=\"https:\/\/devblogs.microsoft.com\/cppblog\/msvc-cpp20-and-the-std-cpp20-switch\/#c++20-[[no_unique_address]]\"> special treatment for the Microsoft Visual C++ compiler<\/a> due to the ABI-breaking nature of <code>[[no_<wbr \/>unique_<wbr \/>address]]<\/code>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you need to run after a failed construction, you have to put it in a base class or member variable.<\/p>\n","protected":false},"author":1069,"featured_media":110434,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-110783","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>If you need to run after a failed construction, you have to put it in a base class or member variable.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110783","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=110783"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/110783\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/110434"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=110783"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=110783"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=110783"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}