{"id":186,"date":"2023-09-27T22:33:28","date_gmt":"2023-09-27T20:33:28","guid":{"rendered":"https:\/\/www.arnorehn.de\/blog\/?p=186"},"modified":"2023-09-27T22:35:03","modified_gmt":"2023-09-27T20:35:03","slug":"pitfalls-of-lambda-capture-initialization","status":"publish","type":"post","link":"https:\/\/www.arnorehn.de\/blog\/2023\/09\/27\/pitfalls-of-lambda-capture-initialization\/","title":{"rendered":"Pitfalls of lambda capture initialization"},"content":{"rendered":"\n<p>Recently, I&#8217;ve stumbled across some behavior of C++ lambda captures that has, initially, made absolutely no sense to me. Apparently, I wasn&#8217;t alone with this, because it has <a href=\"https:\/\/bugreports.qt.io\/browse\/QTBUG-116731\">resulted in a memory leak<\/a> in <code>QtFuture::whenAll()<\/code> and <code>QtFuture::whenAny()<\/code> (now fixed; more on that further down).<\/p>\n\n\n\n<p>I find the corner cases of C++ quite interesting, so I wanted to share this. Luckily, we can discuss this without getting knee-deep into the internals of <code>QtFuture<\/code>. So, without further ado:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Time for an example<\/h2>\n\n\n\n<p>Consider this (<a href=\"https:\/\/gcc.godbolt.org\/z\/j5sxGEjcx\">godbolt<\/a>):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">#include &lt;iostream>\n#include &lt;functional>\n#include &lt;memory>\n#include &lt;cassert>\n#include &lt;vector>\n\nstruct Job\n{\n    template&lt;class T>\n    Job(T &amp;&amp;func) : func(std::forward&lt;T>(func)) {}\n\n    void run() { func(); hasRun = true; }\n\n    std::function&lt;void()> func;\n    bool hasRun = false;\n};\n\nstd::vector&lt;Job> jobs;\n\ntemplate&lt;class T>\nvoid enqueueJob(T &amp;&amp;func)\n{\n    jobs.emplace_back([func=std::forward&lt;T>(func)]() mutable {\n        std::cout &lt;&lt; \"Starting job...\" &lt;&lt; std::endl;\n        \/\/ Move func to ensure that it is destroyed after running\n        auto fn = std::move(func);\n        fn();\n        std::cout &lt;&lt; \"Job finished.\" &lt;&lt; std::endl;\n    });\n}\n\nint main()\n{\n    struct Data {};\n    std::weak_ptr&lt;Data> observer;\n    {\n        auto context = std::make_shared&lt;Data>();\n        observer = context;\n        enqueueJob([context] {\n            std::cout &lt;&lt; \"Running...\" &lt;&lt; std::endl;\n        });\n    }\n    for (auto &amp;job : jobs) {\n        job.run();\n    }\n    assert((observer.use_count() == 0) \n                &amp;&amp; \"There's still shared data left!\");\n}\n<\/pre>\n\n\n\n<p>Output:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Starting job...\nRunning...\nJob finished.<\/pre>\n\n\n\n<p>The code is fairly straight forward. There&#8217;s a list of jobs to which we can be append with <code>enqueueJob()<\/code>. <code>enqueueJob()<\/code> wraps the passed callable with some debug output and ensures that it is destroyed after calling it. The <code>Job<\/code> objects themselves are kept around a little longer; we can imagine doing something with them, even though the jobs have already been run.<br>In <code>main()<\/code>, we enqueue a job that captures some shared state <code>Data<\/code>, run all jobs, and finally assert that the shared <code>Data<\/code> has been destroyed. So far, so good.<\/p>\n\n\n\n<p>Now you might have some issues with the code. Apart from the structure, which, arguably, is a little forced, you might think &#8220;<code>context<\/code> is never modified, so it should be <code>const<\/code>!&#8221;. And you&#8217;re right, that would be better. So let&#8217;s change it (<a href=\"https:\/\/gcc.godbolt.org\/z\/vYYE1WqP5\">godbolt<\/a>):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"diff\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">--- old\n+++ new\n@@ -34,7 +34,7 @@\n     struct Data {};\n     std::weak_ptr&lt;Data> observer;\n     {\n-        auto context = std::make_shared&lt;Data>();\n+        const auto context = std::make_shared&lt;Data>();\n         observer = context;\n         enqueueJob([context] {\n             std::cout &lt;&lt; \"Running...\" &lt;&lt; std::endl;\n<\/pre>\n\n\n\n<p>Looks like a trivial change, right? But when we run it, the assertion fails now!<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"false\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">int main(): Assertion `(observer.use_count() == 0) &amp;&amp; \"There's still shared data left!\"' failed.<\/pre>\n\n\n\n<p>How can this be? We&#8217;ve just declared a variable <code>const<\/code> that <em>isn&#8217;t even used once<\/em>! This does not seem to make any sense. <br>But it gets better: we can fix this by adding what looks like a no-op (<a href=\"https:\/\/gcc.godbolt.org\/z\/bervEaKqq\">godbolt<\/a>):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"diff\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">--- old\n+++ new\n@@ -34,9 +34,9 @@\n     struct Data {};\n     std::weak_ptr&lt;Data> observer;\n     {\n-        auto context = std::make_shared&lt;Data>();\n+        const auto context = std::make_shared&lt;Data>();\n         observer = context;\n-        enqueueJob([context] {\n+        enqueueJob([context=context] {\n             std::cout &lt;&lt; \"Running...\" &lt;&lt; std::endl;\n         });\n     }<\/pre>\n\n\n\n<p>Wait, what? We just have to tell the compiler that we <em>really<\/em> want to capture <code>context<\/code> by the name <code>context<\/code> &#8211; and then it will correctly destroy the shared data? Would this be an application for <a href=\"https:\/\/www.kdab.com\/cpp23-will-be-really-awesome\/\">the <code>really<\/code> keyword<\/a>? Whatever it is, it works; you can <a href=\"https:\/\/gcc.godbolt.org\/z\/vYYE1WqP5\">check it on godbolt<\/a> yourself.<\/p>\n\n\n\n<p>When I first stumbled across this behavior, I just couldn&#8217;t wrap my head around it. I was about to think &#8220;compiler bug&#8221;, as unlikely as that may be. But GCC and Clang <em>both<\/em> behave like this, so it&#8217;s pretty much guaranteed <strong>not<\/strong> to be a compiler bug.<\/p>\n\n\n\n<p>So, after combing through the interwebs, I&#8217;ve found <a href=\"https:\/\/stackoverflow.com\/a\/46349124\/922647\">this StackOverflow answer<\/a> that gives the right hint: <code>[context]<\/code> is not the same as <code>[context=context]<\/code>! The latter drops <code>cv<\/code> qualifiers while the former <strong>does not<\/strong>! Quoting <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/lambda\">cppreference.com<\/a>:<\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Those data members that correspond to captures without initializers are <a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/direct_initialization\">direct-initialized<\/a> when the lambda-expression is evaluated. Those that correspond to captures with initializers are initialized as the initializer requires (could be copy- or direct-initialization). If an array is captured, array elements are direct-initialized in increasing index order. The order in which the data members are initialized is the order in which they are declared (which is unspecified).<\/p>\n<cite><a href=\"https:\/\/en.cppreference.com\/w\/cpp\/language\/lambda\">https:\/\/en.cppreference.com\/w\/cpp\/language\/lambda<\/a><\/cite><\/blockquote>\n\n\n\n<p>So <code>[context]<\/code> will direct-initialize the corresponding data member, whereas <code>[context=context]<\/code> (in this case) does copy-initialization! In terms of code this means:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>[context]<\/code> is equivalent to <code>decltype(context) captured_context{context};<\/code>, i.e. <code>const std::shared_ptr&lt;Data&gt; captured_context{context};<\/code><\/li>\n\n\n\n<li><code>[context=context]<\/code> is equivalent to <code>auto capture_context = context;<\/code>, i.e. <code>std::shared_ptr&lt;Data&gt; captured_context = context;<\/code><\/li>\n<\/ul>\n\n\n\n<p>Good, so writing <code>[context=context]<\/code> actually drops the <code>const<\/code> qualifier on the captured variable! Thus, for the lambda, it is equivalent to not having written it in the first place and using direct-initialization.<\/p>\n\n\n\n<p>But why does this even matter? Why do we leak references to the <code>shared_ptr&lt;Data&gt;<\/code> if the captured variable is <code>const<\/code>? We only ever <code>std::move()<\/code> or <code>std::forward()<\/code> the lambda, right up to the place where we invoke it. After that, it goes out of scope, and all captures should be destroyed as well. Right?<\/p>\n\n\n\n<p>Nearly. Let&#8217;s think about the compiler generates for us when we write a lambda. For the direct-initialization capture (i.e. <code>[context]() {}<\/code>), the compiler roughly generates something like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">struct lambda\n{\n    const std::shared_ptr&lt;Data> context;\n    \/\/ ...\n};<\/pre>\n\n\n\n<p>This is what we want to to <code>std::move()<\/code> around. But it contains a <code>const<\/code> data member, and that <em>cannot be moved from<\/em> (it&#8217;s <code>const<\/code> after all)! So even with <code>std::move()<\/code>, there&#8217;s still a part of the lambda that lingers, keeping a reference to <code>context<\/code>. In the example above, the lingering part is in <code>func<\/code>, the capture of the wrapper lambda created in <code>enqueueJob()<\/code>. We move from <code>func<\/code> to ensure that all captures are destroyed when the it goes out of scope. But for the <code>const std::shared_ptr&lt;Data&gt; context<\/code>, which is hidden inside <code>func<\/code>, this does not work. It keeps holding the reference. The wrapper lambda itself would have to be destroyed for the reference count to drop to zero. <br>However, we keep the already-finished jobs around, so this never happens. The assertion fails.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How does this matter for Qt?<\/h2>\n\n\n\n<p><a href=\"https:\/\/github.com\/qt\/qtbase\/blob\/bb51f0d81437c1471069e8758cefcc090377aa51\/src\/corelib\/thread\/qfuture_impl.h#L1126\"><code>QtFuture::whenAll()<\/code><\/a> and <code>whenAny(<\/code>) create a <code>shared_ptr<\/code> to a <code>Context<\/code> struct and capture that in two lambdas used as continuations on a <code>QFuture<\/code>. Upon completion, the <code>Context<\/code> stores a reference to the <code>QFuture<\/code>. Similar to what we have seen above, continuations attached to <code>QFuture<\/code> are also wrapped by another lambda before being stored. When invoked, the &#8220;inner&#8221; lambda is supposed to be destroyed, while the outer (wrapper) one is kept alive.<\/p>\n\n\n\n<p>In contrast to our example, the <code>QFuture<\/code> situation had created an actual memory leak, though (<a href=\"https:\/\/bugreports.qt.io\/browse\/QTBUG-116731\">QTBUG-116731<\/a>): The &#8220;inner&#8221; continuation references the <code>Context<\/code>, which references the <code>QFuture<\/code>, which again references the continuation lambda, referencing the <code>Context<\/code>. The &#8220;inner&#8221; continuation could not be <code>std::move()<\/code>d and destroyed after invocation, because the <code>std::shared_ptr<\/code> data member was <code>const<\/code>.  This had created a reference cycle, leaking memory. I&#8217;ve also cooked this more complex case down to a small example (<a href=\"https:\/\/gcc.godbolt.org\/z\/nqqE8EWTx\">godbolt<\/a>).<\/p>\n\n\n\n<p>The <a href=\"https:\/\/codereview.qt-project.org\/c\/qt\/qtbase\/+\/501834\">patch for all of this<\/a> is very small. As in the example, it simply consists of making the capture <code>[context=context]<\/code>. It&#8217;s included in the upcoming Qt 6.6.0.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Bottom line<\/h2>\n\n\n\n<p>I seriously didn&#8217;t expect there to be these differences in initialization of by-value lambda captures. Why doesn&#8217;t <code>[context]<\/code> alone also do direct- or copy-initialization, i.e. be exactly the same as <code>[context=context]<\/code>? That would be the sane thing to do, I think. I guess there is some reasoning for this; but I couldn&#8217;t find it (yet). It probably also doesn&#8217;t make a difference in the vast majority of cases.<\/p>\n\n\n\n<p>In any case, I liked hunting this one down and getting to know another one of those dark corners of the C++ spec. So it&#8217;s not all bad &#x1f609;.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Recently, I&#8217;ve stumbled across some behavior of C++ lambda captures that has, initially, made absolutely no sense to me. Apparently, I wasn&#8217;t alone with this, because it has resulted in a memory leak in QtFuture::whenAll() and QtFuture::whenAny() (now fixed; more on that further down). I find the corner cases of C++ quite interesting, so I [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[30,32],"tags":[37,23,38,22],"class_list":["post-186","post","type-post","status-publish","format-standard","hentry","category-programming","category-qt","tag-c-2","tag-lambda","tag-memory-leak","tag-qt"],"_links":{"self":[{"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/posts\/186","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/comments?post=186"}],"version-history":[{"count":46,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/posts\/186\/revisions"}],"predecessor-version":[{"id":232,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/posts\/186\/revisions\/232"}],"wp:attachment":[{"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/media?parent=186"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/categories?post=186"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.arnorehn.de\/blog\/wp-json\/wp\/v2\/tags?post=186"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}