I cannot understand why the captured value is lost. I understand that it is connected with an out of scope or copying of an object of LambdaWrapper. But what exactly happens? If LambdaWrapper(100) leaves the scope in Add and reference to __value loses, then why there is no same to LambdaWrapper(300).
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
class LambdaWrapper {
public:
LambdaWrapper(double new_value): __value (new_value) {
cout << "constructed with " << __value << endl;
__func = [this](){ cout << __value << endl;};
}
void call() const { __func(); }
private:
double __value;
function<void(void)> __func;
};
class LambdaContainer {
public:
void Add(double value) {
LambdaWrapper w(value); //out of scope
parts.push_back(w);
}
void Add(LambdaWrapper w) // passed as value
{
parts.push_back(w);
}
void call() const {
for (const auto& part : parts)
part.call();
}
private:
vector<LambdaWrapper> parts;
};
int main() {
LambdaContainer c;
c.Add(100);
LambdaWrapper w(200);
c.Add(w);
c.Add( LambdaWrapper(300) ); //LambdaWrapper(300) will out of scope too
cout << "==============" << endl;
c.call();
return 0;
}
The output:
constructed with 100 constructed with 200 constructed with 300 ============== 6.95168e-308 <<< WHY? 200 300
I suppose this question is asking what happens, rather than "Is it ok", so in this case (usually) gdb
is your friend, modifying the program to print the address of this during construction, this inside __func and the actual address of the object in the container we see:
(addresses varies, but distances and concepts should remain the same)
# During constructors and captured value:
0x7fffffffda80 <- LambdaWrapper(100)
0x7fffffffdb00 <- LambdaWrapper(200)
0x7fffffffdb60 <- LambdaWrapper(300)
# Actual address of data stored in the container:
0x6170c0 <- LambdaWrapper(100)
0x6170e8 <- LambdaWrapper(200)
0x617110 <- LambdaWrapper(300)
There is a huge value difference, this is because the creation happens on the stack, while the vector allocates data with new
on the heap.
From gdb
calling info proc mappings
we obtain the memory addresses list:
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x400000 0x404000 0x4000 0x0 /[...]/LambdaOutOfScope/a.out
0x603000 0x604000 0x1000 0x3000 /[...]/LambdaOutOfScope/a.out
0x604000 0x605000 0x1000 0x4000 /[...]/LambdaOutOfScope/a.out
0x605000 0x637000 0x32000 0x0 [heap]
[...]
0x7ffffffde000 0x7ffffffff000 0x21000 0x0 [stack]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 [vsyscall]
But this does not answer why only the 100 is changed.
The answer to the problem resides in the stack frames:
each function call has a local (usually small) variable space for static variables (those without new
for simplicity).
If we check the stack info with info frame
we see that:
(gdb) info frame
Stack level 0, frame at 0x7fffffffdbb0:
rip = 0x400deb in main (main.cpp:75); saved rip = 0x7ffff7495830
source language c++.
Arglist at 0x7fffffffdba0, args:
Locals at **0x7fffffffdba0**, Previous frame's sp is 0x7fffffffdbb0
Saved registers:
rbx at 0x7fffffffdb98, rbp at 0x7fffffffdba0, rip at 0x7fffffffdba8
Inside main, so the 100 stays out of this frame, because it is not constructed in main but in Add
, to check, when inside Add we get:
(gdb) info frame 1
Stack frame at 0x1:
rip = 0x0; saved rip = <not saved>
Outermost frame: previous frame identical to this frame (corrupt stack?)
Arglist at 0x7fffffffdac8, args:
Locals at **0x7fffffffdac8**, Previous frame's sp is 0x7fffffffdad8
Saved registers:
rip at 0x7fffffffdad0
So the corruption happens when we call another function, but since the elements allocated in main are local they are preserved, if you put a c.Add(400);
after the 300 you'll see it corrupted too (even if constructed after).
NOTE: I hope to have covered all, but gdb usage in detail, there are plenty of guides on the internet.