2

I have a problem with a lambda function in C++: I'm trying to define an asynchronous loader that fills an array of object given a list of string as input.

The code looks like this (not exactly, but I hope you get the idea):

void loadData() {
    while (we_have_data()) {
        std::string str = getNext();
        array.resize(array.size() + 1);
        element &e = array.back();
        tasks.push_back([&, str] () {
            std::istringstream iss(str);
            iss >> e;
        }
    }

    for (auto task: tasks) {
        task();
    }
}

When at the end I scan the list of tasks and execute them, the application crashes on the first access to the variable e inside the lambda. If I run inside a debugger, I can find the right values inside the object e itself. I am doing something wrong, but I don't really understand what.

1
  • 1
    is array a std::vector that undergoes relocation ? Commented Apr 13, 2016 at 13:27

4 Answers 4

4

You are holding a dangling reference. When you do

tasks.push_back([&, str] () {
    std::istringstream iss(str);
    iss >> e;
}

You capture by reference the element returned by array.back() since a reference to e is actually a reference to whatever e refers to. Unfortunately resize is called in the while loop so when array is resized the references to back() are invalidated and you are now referring to an object that no longer exist.

Sign up to request clarification or add additional context in comments.

2 Comments

[&] captures a reference to array.back(), not a reference to reference, I guess, and it becomes dangling when array is resized, I think
@PiotrSkotnicki Yeah just noticed that. I have updated the answer.
4

The scope of element& e is the while-loop.

After every iteration of the while-loop, you have lambda functions with a captured reference to different e's, which have all gone out-of-scope.

1 Comment

Sniped by NathanOliver
2

You capture e (aka. array.back()) "by-reference" when creating the lambda, with subsequent resizes of the array (with possible reallocations), leaves a dangling reference and in turn causes an error when you attempt to access this dangling reference. Any attempt (not limited to the lambda) to access elements in the array by a previously assigned reference after the array has undergone a resize (and reallocation) will cause a "dangling reference" problem.

An alternative... instead of the two loops, why not just execute the task immediately in the while loop and forgo the dangling reference and attempting to get pointer or iterator based alternatives working.

Further alternative... if the elements in the array can be shared, a std::shared_ptr solution could work, the caveat would be to capture the shared_ptr elements by value, thus ensuring the lambda shared shares ownership of those elements with the array as it is resized.

A sampling of the idea...

void loadData() {
    while (we_have_data()) {
        std::string str = getNext();
        array.resize(array.size() + 1);
        std::shared_ptr<element> e = array.back();
        tasks.push_back([e, str] () {
            std::istringstream iss(str);
            iss >> *e;
        }
    }

    for (auto task: tasks) {
        task();
    }
}

Comments

0

You have two strikes against you here.

Firstly, you are capturing a reference to an iterator to a vector that is likely to be resized and thus relocated.

Secondly, you are capturing a reference to a local (stack) variable that goes out of scope. Within the loop, the compiler probably uses the same memory location for 'e' each time, so all of the references would point to the same stack location.

A simpler solution would be to store the element number:

while (we_have_data()) {
    std::string str = getNext();
    size_t e = array.size();
    array.resize(e + 1);
    tasks.push_back([&, e, str] () {
        std::istringstream iss(str);
        iss >> array[e];
    }
}

If you have C++14 and your strings are long, you may want to consider:

    tasks.push_back([&, e, str{std::move(str)}] () {

All of this assumes that array will not undergo further manipulations or go out of scope while the tasks are running.

5 Comments

str{std::move(str)} is C++14 ?
Assuming it's a vector (or standard container), the .back() provides a reference to the last element, not an iterator.
and a local reference variable is not captured
I think this is out of bounds: array[e]?
fixed the out of bounds.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.