thesaint
thesaint

Reputation: 1281

C++11: Preventing lambda scope capture bugs

In C++, in contrast to languages such as C#, it is possible to specify if or not enclosing scope variables shall be capture by value or by reference within a lambda expression. This leads to the undefined case in which it is possible to pass a lambda with enclosing scope captured by reference to a function that returns before invoking the lambda expression:

void test()
{
    int t = 1;
    enqueue_task([&]() { do_something(t); });
}

In this case, "t" will most likely be out of scope when that task specified by the lambda expression is scheduled for execution. This leads to ugly bugs, obviously.

MY solution would be a language feature like this:

template<class T>
void enqueue_task(T lambda)
{
    static_assert(!std::is_lambda<T>::value || std::is_lambda_captured_by_value<T>::value,
        "The lambda expression is executed asynchronously and therefore capturing eclosing state via reference is forbidden.");

    // enqueue task for execution
}

To me, this would be a clean "non-invasive" extension that would allow middle-ware writers to protect their API from misuse. Of course it doesn't offer bullet-proof protection, since I still can pass a pointer to a stack object by value and probably much more. Anyway, code that would still silently cause undefined behavior when being passed by value will probably be by itself already questionable.

Is there something similar I can do that is already supported?

To me, a sane solution at the moment seems to simply NOT allow any lambda expression in deferred execution situations. For instance, an event handler should not be allowed to be of a lambda type. Which is easier said than done, since this also implies that I can't use std::function and would have to go back to good old function types.

An even better approach would be to introduce kind of a keyword, like:

void test()
{
    int t = 1;
    enqueue_task(deferred () { do_something(t); });
}

which would make sure that, by all mean a compiler can, the passed lambda function will be suitable for delayed execution, which means when its enclosing scope is gone.

I think C++11 has gone long way to make C++ programming safe. This lambda-thing is one of the few places where you are still pointing a gun at your feet. Its just a ticking timebomb.

Upvotes: 4

Views: 1648

Answers (1)

Yam Marcovic
Yam Marcovic

Reputation: 8141

Usually the remedy is to capture by value [=]() {...}.
When copying the actual object is not viable, it is usually beneficial to use it via a shared_ptr, which might offer cheaper copying, depending on the context, and would also allow you to share the ownership such that both the caller and the deferred lambda use it independently.

C++14 should have move-capture semantics, which would solve the performance problem of copying the object, when sharing is not needed.

Otherwise, passing by-ref is what you want. And like everything in C++, not just in lambdas, when you start passing pointers and references around, you need to be careful.

Upvotes: 6

Related Questions