0
votes

I have trouble understanding what is going on here with calls to C(const C&) and ~C()

So, I make a lambda that captures by value, and then return it. As I understand, each returned lambda has it's own context (the trace is consistent with that claim).

However, according to stroustrup describing of lambda as a function object shortcut (in 11.4.1 of C++ 4th ed), I would expect one copy only to be made for the captured variable, and it does not seem to be the case.

here is my code

//g++  5.4.0
#include <functional>
#include <iostream>
using namespace std;

class C{
    float f; 
  public:
    C(float f): f(f){ cout << "#build C(" << f << ")\n"; }
    C(const C& c): f(c.f+0.1){ cout << "#copy C(" << f << ")\n"; }
    ~C(){ cout << "#destroy C(" << f << ")\n"; }
    void display() const { cout << "this is C(" << f << ")\n"; }
};

std::function<void(void)> make_lambda_val(int i){
    C c{i};
    return [=] () -> void { c.display(); } ;
}

int main(){
    cout << "/**trace\n\n";
    cout << "---  ??  ---\n";
    {
        auto l0 = make_lambda_val(0);
        auto l1 = make_lambda_val(1);
        auto l2 = make_lambda_val(2);
        cout << "ready\n";
        l0();
        l1();
        l2();
    }
    cout << "\n*/\n";
}

and the corresponding trace: (with my comment)

/**trace

---  ??  ---
#build C(0)
#copy C(0.1)     <<--|  2 copies ??
#copy C(0.2)     <<--|  
#destroy C(0.1)  <----  one of which is already discarded ?
#destroy C(0)
#build C(1)
#copy C(1.1)
#copy C(1.2)
#destroy C(1.1)
#destroy C(1)
#build C(2)
#copy C(2.1)
#copy C(2.2)
#destroy C(2.1)
#destroy C(2)
ready
this is C(0.2)   <---- the second copy is kept ?
this is C(1.2)
this is C(2.2)
#destroy C(2.2)
#destroy C(1.2)
#destroy C(0.2)

*/
2

2 Answers

1
votes

I would expect one copy only to be made for the captured variable

And indeed the captured variable is copied once. That is, it's the source in a copy operation only once.

A std::function is not a lambda. Its initialization involves copying the callable object. So when the lambda is copied into the std::function, the variable it holds by value is copied as well. And when the function returns, the lambda temporary is destoryed. What you see is the destruction of the variable inside the lambda you created.

1
votes

Your std::function<void(void)> return type from the lambda making function make_lambda_val(int) is adding an extra layer of copy-complexity to this analysis, which explains the extra copying you've marked in your trace output.

To simplify, replace the extra std::function layer by replacing the make_lambda_val(int) function entirely with the following lambda making lambda object (scoped in main()):

auto make_lambda_val = [](int i){
    C c(i);
    return [=] () -> void { c.display(); };
};

in which case your tracing output looks as expected:

/**trace

---  ??  ---
#build C(0)
#copy C(0.1)
#destroy C(0)
#build C(1)
#copy C(1.1)
#destroy C(1)
#build C(2)
#copy C(2.1)
#destroy C(2)
ready
this is C(0.1)
this is C(1.1)
this is C(2.1)
#destroy C(2.1)
#destroy C(1.1)
#destroy C(0.1)

*/